-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DD-1493: Add command for dataverse role assignment (#53)
* Initial implementation of dv-dataverse-role-assignment * Completed functionality of dv-dataverse-role-assignment * Renaming BatchProcessor to DatasetBatchProcessor * Refactoring of the BatchProcessor classes * Refactoring; dv_dataverse_role_assignment uses new DataverseRole class * Refactoring; removed alias as input paramater for the assigment related members of the dataverse_api * Refactoring; removed alias as input parameter for all members of the dataverse_api * Refactoring; moved and renamed new BatchProcessor out of the way * Cleanup; removed whitespace * Refactoring; moved part of assignment code from roles class back to commandline interface * Refactoring; role assignment in DataverseRole uses the self.dry_run instead of input param
- Loading branch information
Showing
11 changed files
with
353 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import logging | ||
import os | ||
import time | ||
|
||
from datastation.common.csv import CsvReport | ||
from datastation.common.utils import plural | ||
|
||
|
||
# Base class for batch processing of items | ||
class CommonBatchProcessor: | ||
def __init__(self, item_name="item", wait=0.1, fail_on_first_error=True): | ||
self.item_name = item_name | ||
self.wait = wait | ||
self.fail_on_first_error = fail_on_first_error | ||
|
||
def process_items(self, items, callback): | ||
if type(items) is list: | ||
num_items = len(items) | ||
logging.info(f"Start batch processing on {num_items} {plural(self.item_name)}") | ||
else: | ||
logging.info(f"Start batch processing on unknown number of {plural(self.item_name)}") | ||
num_items = -1 | ||
i = 0 | ||
for item in items: | ||
i += 1 | ||
try: | ||
if self.wait > 0 and i > 1: | ||
logging.debug(f"Waiting {self.wait} seconds before processing next {self.item_name}") | ||
time.sleep(self.wait) | ||
logging.info(f"Processing {i} of {num_items}: {item}") | ||
callback(item) | ||
except Exception as e: | ||
logging.exception("Exception occurred", exc_info=True) | ||
if self.fail_on_first_error: | ||
logging.error(f"Stop processing because of an exception: {e}") | ||
break | ||
logging.debug("fail_on_first_error is False, continuing...") | ||
|
||
|
||
def get_provided_items_iterator(item_or_items_file, item_name="item"): | ||
if item_or_items_file is None: | ||
logging.debug(f"No {plural(item_name)} provided.") | ||
return None | ||
elif os.path.isfile(os.path.expanduser(item_or_items_file)): | ||
items = [] | ||
with open(os.path.expanduser(item_or_items_file)) as f: | ||
for line in f: | ||
items.append(line.strip()) | ||
return items | ||
else: | ||
return [item_or_items_file] | ||
|
||
|
||
def get_pids(pid_or_pids_file, search_api=None, query="*", subtree="root", object_type="dataset", dry_run=False): | ||
""" | ||
Args: | ||
pid_or_pids_file: The dataset pid, or a file with a list of pids. | ||
search_api: must be provided if pid_or_pids_file is None | ||
query: passed on to search_api().search | ||
object_type: passed on to search_api().search | ||
subtree (object): passed on to search_api().search | ||
dry_run: Do not perform the action, but show what would be done. | ||
Only applicable if pid_or_pids_file is None. | ||
Returns: an iterator with pids, | ||
if pid_or_pids_file is not provided, it searches for all datasets | ||
and extracts their pids, fetching the result pages lazy. | ||
""" | ||
if pid_or_pids_file is None: | ||
result = search_api.search(query=query, subtree=subtree, object_type=object_type, dry_run=dry_run) | ||
return map(lambda rec: rec['global_id'], result) | ||
else: | ||
return get_provided_items_iterator(pid_or_pids_file, "pid") | ||
|
||
|
||
def get_aliases(alias_or_aliases_file, dry_run=False): | ||
""" | ||
Args: | ||
alias_or_aliases_file: The dataverse alias, or a file with a list of aliases. | ||
dry_run: Do not perform the action, but show what would be done. | ||
Only applicable if pid_or_pids_file is None. | ||
Returns: an iterator with aliases | ||
""" | ||
if alias_or_aliases_file is None: | ||
# The tree of all (published) dataverses could be retrieved and aliases could recursively be extracted | ||
# from the tree, but this is not implemented yet. | ||
logging.warning(f"No aliases provided, nothing to do.") | ||
return None | ||
else: | ||
return get_provided_items_iterator(alias_or_aliases_file, "alias") | ||
|
||
|
||
class DatasetBatchProcessor(CommonBatchProcessor): | ||
|
||
def __init__(self, wait=0.1, fail_on_first_error=True): | ||
super().__init__("pid", wait, fail_on_first_error) | ||
|
||
def process_pids(self, pids, callback): | ||
super().process_items(pids, callback) | ||
|
||
|
||
class DatasetBatchProcessorWithReport(DatasetBatchProcessor): | ||
|
||
def __init__(self, report_file=None, headers=None, wait=0.1, fail_on_first_error=True): | ||
super().__init__(wait, fail_on_first_error) | ||
if headers is None: | ||
headers = ["DOI", "Modified", "Change"] | ||
self.report_file = report_file | ||
self.headers = headers | ||
|
||
def process_pids(self, pids, callback): | ||
with CsvReport(os.path.expanduser(self.report_file), self.headers) as csv_report: | ||
super().process_pids(pids, lambda pid: callback(pid, csv_report)) | ||
|
||
|
||
class DataverseBatchProcessor(CommonBatchProcessor): | ||
|
||
def __init__(self, wait=0.1, fail_on_first_error=True): | ||
super().__init__("alias", wait, fail_on_first_error) | ||
|
||
def process_aliases(self, aliases, callback): | ||
super().process_items(aliases, callback) | ||
|
||
|
||
class DataverseBatchProcessorWithReport(DataverseBatchProcessor): | ||
|
||
def __init__(self, report_file=None, headers=None, wait=0.1, fail_on_first_error=True): | ||
super().__init__(wait, fail_on_first_error) | ||
if headers is None: | ||
headers = ["alias", "Modified", "Change"] | ||
self.report_file = report_file | ||
self.headers = headers | ||
|
||
def process_aliases(self, aliases, callback): | ||
with CsvReport(os.path.expanduser(self.report_file), self.headers) as csv_report: | ||
super().process_aliases(aliases, lambda alias: callback(alias, csv_report)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import rich | ||
from datetime import datetime | ||
|
||
from datastation.common.common_batch_processing import DataverseBatchProcessorWithReport, get_aliases | ||
from datastation.dataverse.dataverse_api import DataverseApi | ||
from datastation.dataverse.dataverse_client import DataverseClient | ||
|
||
|
||
class DataverseRole: | ||
|
||
def __init__(self, dataverse_client: DataverseClient, dry_run: bool = False): | ||
self.dataverse_client = dataverse_client | ||
self.dry_run = dry_run | ||
|
||
def list_role_assignments(self, alias): | ||
r = self.dataverse_client.dataverse(alias).get_role_assignments() | ||
if r is not None: | ||
rich.print_json(data=r) | ||
|
||
def add_role_assignment(self, role_assignment, dataverse_api: DataverseApi, csv_report): | ||
assignee = role_assignment.split('=')[0] | ||
role = role_assignment.split('=')[1] | ||
action = "None" | ||
if self.in_current_assignments(assignee, role, dataverse_api): | ||
print("{} is already {} for dataset {}".format(assignee, role, dataverse_api.get_alias())) | ||
else: | ||
print( | ||
"Adding {} as {} for dataset {}".format(assignee, role, dataverse_api.get_alias())) | ||
dataverse_api.add_role_assignment(assignee, role, dry_run=self.dry_run) | ||
action = "Added" | ||
csv_report.write( | ||
{'alias': dataverse_api.get_alias(), 'Modified': datetime.now(), 'Assignee': assignee, 'Role': role, | ||
'Change': action}) | ||
|
||
def in_current_assignments(self, assignee, role, dataverse_api: DataverseApi): | ||
current_assignments = dataverse_api.get_role_assignments() | ||
found = False | ||
for current_assignment in current_assignments: | ||
if current_assignment.get('assignee') == assignee and current_assignment.get( | ||
'_roleAlias') == role: | ||
found = True | ||
break | ||
return found | ||
|
||
|
||
def remove_role_assignment(self, role_assignment, dataverse_api: DataverseApi, csv_report): | ||
assignee = role_assignment.split('=')[0] | ||
role = role_assignment.split('=')[1] | ||
action = "None" | ||
if self.in_current_assignments(assignee, role, dataverse_api): | ||
print("Removing {} as {} for dataverse {}".format(assignee, role, dataverse_api.get_alias())) | ||
all_assignments = dataverse_api.get_role_assignments() | ||
for assignment in all_assignments: | ||
if assignment.get('assignee') == assignee and assignment.get('_roleAlias') == role: | ||
dataverse_api.remove_role_assignment(assignment.get('id'), dry_run=self.dry_run) | ||
action = "Removed" | ||
break | ||
else: | ||
print("{} is not {} for dataverse {}".format(assignee, role, dataverse_api.get_alias())) | ||
csv_report.write( | ||
{'alias': dataverse_api.get_alias(), 'Modified': datetime.now(), 'Assignee': assignee, 'Role': role, | ||
'Change': action}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.