diff --git a/eossr/api/__init__.py b/eossr/api/__init__.py index deb512f82e942e6c32d58442e377ecb3e6419558..21f66710192b8875d186548ad7ad6b65607231b8 100644 --- a/eossr/api/__init__.py +++ b/eossr/api/__init__.py @@ -2,11 +2,13 @@ import requests from . import zenodo -from .zenodo import Record, get_zenodo_records, zenodo_api_url +from .zenodo import Record, get_zenodo_records, zenodo_api_url, ZenodoAPI +from .zenodo.http_status import ZenodoHTTPStatus __all__ = [ 'zenodo', 'get_ossr_records', + 'get_ossr_pending_requests', ] escape_community = 'escape2020' @@ -60,3 +62,18 @@ def get_ossr_records(search='', sandbox=False, **kwargs): return get_zenodo_records(search, sandbox=sandbox, **kwargs) + +def get_ossr_pending_requests(**params): + """ + Get a list of records that have been requested to be added to the OSSR. + + :param community: str + Name of the community. + :param params: dict + Parameters for the request. Override the class parameters. + :return: + """ + zen = ZenodoAPI() + return zen.get_community_pending_requests(escape_community, params) + + diff --git a/eossr/api/zenodo/__init__.py b/eossr/api/zenodo/__init__.py index 653229a0d3c94e42f2645025af6e66cbe1932717..f7716a6513968c1a656b0db241e0192e90561b70 100644 --- a/eossr/api/zenodo/__init__.py +++ b/eossr/api/zenodo/__init__.py @@ -10,7 +10,9 @@ from urllib.parse import urlencode from urllib.request import urlopen from bs4 import BeautifulSoup from zipfile import ZipFile -from eossr import ROOT_DIR +from copy import deepcopy +from subprocess import run +from ... import ROOT_DIR from . import http_status from ...metadata.codemeta2zenodo import parse_codemeta_and_write_zenodo_metadata_file, converter from ...utils import get_codemeta_from_zipurl @@ -162,7 +164,7 @@ class ZenodoAPI: http_status.ZenodoHTTPStatus(upload.status_code, upload.json()) return upload - def update_metadata_entry(self, entry_id, json_metadata): + def set_deposit_metadata(self, deposit_id, json_metadata): """ Update an entry resource. Data should be the entry information that will be shown when a deposition is visited at the Zenodo site. @@ -176,7 +178,7 @@ class ZenodoAPI: :return: request.put method """ - url = f"{self.api_url}/deposit/depositions/{entry_id}" + url = f"{self.api_url}/deposit/depositions/{deposit_id}" headers = {"Content-Type": "application/json"} # The metadata field is already created, just need to be updated. @@ -438,7 +440,7 @@ class ZenodoAPI: print(f" * {file.name} uploaded") # and update metadata - self.update_metadata_entry(entry_id=new_record_id, json_metadata=metadata) + self.set_deposit_metadata(new_record_id, json_metadata=metadata) print(" * Metadata updated successfully") # publish new record @@ -504,27 +506,26 @@ class ZenodoAPI: test_entry_id = new_entry.json()['id'] with open(self.path_zenodo_file) as file: metadata_entry = json.load(file) - update_metadata = self.update_metadata_entry(test_entry_id, json_metadata=metadata_entry) + updated_metadata = self.set_deposit_metadata(test_entry_id, json_metadata=metadata_entry) try: - http_status.ZenodoHTTPStatus(update_metadata.status_code) - print(" * Update metadata status OK !") + http_status.ZenodoHTTPStatus(updated_metadata.status_code) + print(" * Metadata deposit status OK !") pprint.pprint(metadata_entry) except http_status.HTTPStatusError: - print(" ! ERROR while testing update of metadata\n", update_metadata.json()) - print(" ! Erasing dummy test entry...\n") - try: - http_status.ZenodoHTTPStatus(update_metadata.status_code, update_metadata.json()) - except http_status.HTTPStatusError: - print(f" !! ERROR erasing dummy test entry. Please erase it manually at\n " - f"{self.api_url[:-4]}/deposit") - sys.exit(-1) + print(" ! ERROR while testing update of metadata\n", updated_metadata.json()) + print(" ! The deposit will be deleted") # 4 - Test delete entry - print("4 --> Testing the deletion of the dummy entry...") + print("4 --> Deleting the dummy entry...") delete_test_entry = self.erase_entry(test_entry_id) + try: + http_status.ZenodoHTTPStatus(delete_test_entry.status_code) + except: + print(f" !! ERROR erasing dummy test entry: {delete_test_entry.json()}") + print(f"Please erase it manually at {self.api_url[:-4]}/deposit") + sys.exit(-1) - http_status.ZenodoHTTPStatus(delete_test_entry.status_code) print(" * Delete test entry status OK !") print("\n\tYAY ! Successful testing of the connection to Zenodo ! \n\n" @@ -561,6 +562,71 @@ class ZenodoAPI: return similar_records + def get_community_pending_requests(self, community, **params): + """ + Get a list of records that have been requested to be added to a community. + + :param community: str + Name of the community. + :param params: dict + Parameters for the request. Override the class parameters. + :return: [Record] + """ + url = self.api_url + f'/records/?q=provisional_communities:{community.lower()}' + parameters = deepcopy(self.parameters) + parameters.update(params) + req = requests.get(url, params=parameters) + http_status.ZenodoHTTPStatus(req.status_code, json=req.json()) + req_json = req.json() + records = [Record(rec) for rec in req_json['hits']['hits']] + return records + + def accept_pending_request(self, community, record_id): + """ + Accept a pending request into a community. + The community must be owned by the token owner. + + :param community: str + community name. The community must be owned by the token owner. + :param record_id: + str or int + """ + raise NotImplementedError("Sorry") + # TODO: this should work based on https://github.com/zenodo/zenodo/issues/1436 + # run(['curl', '-i', '-X', 'POST', '-H', + # 'Content-Type:application/json', '--data', + # f'{"action":"accept", "recid:{record_id}"}', + # f'"https://zenodo.org/communities/{community}/curate/"', + # ]) + + def update_record_metadata(self, record_id, metadata): + """ + Update a published record metadata + + :param record_id: int + :param metadata: dict + :return: `requests.response` + """ + req = requests.post(f"{self.api_url}/deposit/depositions/{record_id}/actions/edit?access_token={self.access_token}") + if req.status_code == 403: + # In this case it is fine to continue editing the record metadata + warnings.warn("The record was already open for edition") + else: + http_status.ZenodoHTTPStatus(req.status_code, req.json()) + + record = get_record(record_id, sandbox=self.sandbox) + record_metadata = record.data['metadata'] + record_metadata['upload_type'] = record_metadata['resource_type']['type'] + record_metadata.pop('access_right_category') + record_metadata.pop('relations') + record_metadata.pop('related_identifiers') + record_metadata.pop('resource_type') + record_metadata.update(metadata) + self.set_deposit_metadata(record_id, json_metadata=record_metadata) + req = self.publish_entry(record_id) + return req + + class SimilarRecordError(Exception): pass @@ -653,7 +719,7 @@ class Record: :param linebreak: string line break character. default: '\n' - :param file: a file-like object (stream); defaults to the current sys.stdout. + :param file: a file-like object (stream); defaults to the current sys.stdout. :return: """ metadata = self.data['metadata'] @@ -807,3 +873,5 @@ def get_record(record_id, sandbox=False): raise ValueError(f"Error {answer['status']} : {answer['message']}") else: return Record(answer) + + diff --git a/eossr/api/zenodo/tests/test_zenodo.py b/eossr/api/zenodo/tests/test_zenodo.py index 2fcc3acb04d98f9160ccb389766f90ff2cf2b835..6e001a98ef919a3acb1c2e2a992bcdd35ee9bd80 100644 --- a/eossr/api/zenodo/tests/test_zenodo.py +++ b/eossr/api/zenodo/tests/test_zenodo.py @@ -121,6 +121,37 @@ class TestZenodoAPIToken(unittest.TestCase): not_existing_record = Record.from_id(767507, sandbox=True) # Sandbox E.GARCIA ZenodoCI_vTest Records assert self.zenodo.find_similar_records(not_existing_record) == [] + def test_get_community_pending_requests(self): + z = self.zenodo + records = z.get_community_pending_requests('escape2020') + records_ids = [rec.id for rec in records] + assert 532458 in records_ids # pending request in escape2020 sandbox community - 2021-11-28 + + @pytest.mark.skipif(os.getenv('SANDBOX_ZENODO_TOKEN_GARCIA') is None, + reason="SANDBOX_ZENODO_TOKEN_GARCIA not defined") + def test_pending_request(self): + zk = ZenodoAPI(os.getenv('SANDBOX_ZENODO_TOKEN_GARCIA'), sandbox=True) + record_id = 970583 + from eossr.api import escape_community + + ## Add to escape2020 community and test request + meta = {'communities': [{'identifier': escape_community}]} + zk.update_record_metadata(record_id, meta) + records = self.zenodo.get_community_pending_requests(escape_community) + assert record_id in [rec.id for rec in records] + + # TODO: After accept_pending_request is implemented + # self.zenodo.accept_pending_request(escape_community, record_id) + # records = get_zenodo_records(community=escape_community, sandbox=True, size=4) + # assert record_id in [rec.id for rec in records] + + ## Remove from community + meta = {'communities': []} + zk.update_record_metadata(record_id, meta) + records = get_zenodo_records(community=escape_community, sandbox=True, size=4) + assert record_id not in [rec.id for rec in records] + + def test_get_zenodo_records(): l = get_zenodo_records('ESCAPE template project') @@ -161,3 +192,4 @@ def test_write_record_zenodo(test_get_record_4923992, tmpdir): with open(tmpdir/'.zenodo.json') as file: json_dict = json.load(file) assert json_dict['conceptdoi'] == '10.5281/zenodo.3572654' +