def create_doi(doi): """ :param doi: Creates a DOI using the data provider. If it already exists, we return back the existing provider. :return: DataCiteProvider """ try: return DataCiteProvider.create(doi) except DataCiteUnauthorizedError: log.error('Unable to mint DOI. No authorisation credentials provided.') except Exception: return DataCiteProvider.get(doi, 'doi')
def patch(self, pid, record, **kwargs): """Modify a record. The data should be a JSON-patch, which will be applied to the record. Procedure description: #. The record is deserialized using the proper loader. #. The ETag is checked. #. The record is patched. #. The HTTP response is built with the help of the link factory. :param pid: Persistent identifier for record. :param record: Record object. :returns: The modified record. """ data = self.loaders[request.mimetype]() if data is None: raise InvalidDataRESTError() self.check_etag(str(record.revision_id)) try: record = record.patch(data) except (JsonPatchException, JsonPointerException): raise PatchJSONFailureRESTError() record.commit() db.session.commit() if not (current_app.config.get('TESTING', False) or current_app.config.get('FAKE_DOI', False)): doi = None for rec_pid in record['_pid']: if rec_pid['type'] == 'DOI': doi = rec_pid doi_pid = PersistentIdentifier.get('doi', doi['value']) if doi_pid: from .serializers import datacite_v44 from .minters import make_record_url try: datacite_provider = DataCiteProvider(doi_pid) doc = datacite_v44.serialize(doi_pid, record) url = make_record_url(pid.pid_value) datacite_provider.update(url, doc) except: current_app.logger.error( "Error in DataCite metadata update", exc_info=True) return self.make_response(pid, record, links_factory=self.links_factory)
def test_datacite_reserve_register_update_delete(app, db): """Test datacite provider reserve.""" with app.app_context(): api = MagicMock() provider = DataCiteProvider.create('10.1234/a', client=api) assert provider.reserve('mydoc') assert provider.pid.status == PIDStatus.RESERVED api.metadata_post.assert_called_with('mydoc') assert provider.register('myurl', 'anotherdoc') assert provider.pid.status == PIDStatus.REGISTERED api.metadata_post.assert_called_with('anotherdoc') api.doi_post.assert_called_with('10.1234/a', 'myurl') assert provider.update('anotherurl', 'yetanother') assert provider.pid.status == PIDStatus.REGISTERED api.metadata_post.assert_called_with('yetanother') api.doi_post.assert_called_with('10.1234/a', 'anotherurl') assert provider.delete() assert provider.pid.status == PIDStatus.DELETED api.metadata_delete.assert_called_with('10.1234/a') assert provider.update('newurl', 'newdoc') assert provider.pid.status == PIDStatus.REGISTERED api.metadata_post.assert_called_with('newdoc') api.doi_post.assert_called_with('10.1234/a', 'newurl')
def register_doi(doi, url, xml, uuid): """ Given a data submission id, this method takes it's assigned DOI, creates the DataCite XML, and registers the DOI. :param data_submissions: :param recid: :return: """ log.info('{0} - {1}'.format(doi, url)) try: provider = DataCiteProvider.get(doi, 'doi') except PIDDoesNotExistError: provider = create_doi(doi) pidstore_obj = PersistentIdentifier.query.filter_by(pid_value=doi).first() if pidstore_obj: pidstore_obj.object_uuid = uuid db.session.add(pidstore_obj) db.session.commit() try: provider.register(url, xml) except DataCiteUnauthorizedError: log.error('Unable to mint DOI. No authorisation credentials provided.') except PIDInvalidAction: provider.update(url, xml) except IntegrityError: provider.update(url, xml) except DataCiteError as dce: log.error('Error registering {0} for URL {1}\n\n{2}' .format(doi, url, dce))
def check_record_doi(record, update=False): """ Checks that the DOI of a record is registered.""" recid = record.get('_deposit', {}).get('id') click.secho('checking DOI for record {}'.format(recid)) doi_list = [ DataCiteProvider.get(d.get('value')) for d in record['_pid'] if d.get('type') == 'DOI' ] for doi in doi_list: if _datacite_doi_reference(doi.pid.pid_value) is None: if doi.pid.status == PIDStatus.REGISTERED: # the doi is not truly registered with datacite click.secho(' {}: not registered with datacite'.format( doi.pid.pid_value)) doi.pid.status = PIDStatus.RESERVED click.secho(' {}: {}'.format(doi.pid.pid_value, doi.pid.status)) if doi.pid.status != PIDStatus.RESERVED: continue # RESERVED but not REGISTERED if update: recid = record.get('_deposit', {}).get('id') url = make_record_url(recid) doc = datacite_v31.serialize(doi.pid, record) _datacite_register_doi(doi, url, doc) db.session.commit() click.secho(' registered just now', fg='green', bold=True) else: click.secho(' not registered', fg='red', bold=True)
def datacite_register(self, pid_value, record_uuid, max_retries=5, countdown=5): """Mint the DOI with DataCite. :param pid_value: Value of record PID, with pid_type='recid'. :type pid_value: str """ try: record = Record.get_record(record_uuid) # Bail out if not a CDS DOI. if not is_local_doi(record['doi']) or \ not current_app.config['DEPOSIT_DATACITE_MINTING_ENABLED']: return dcp = DataCiteProvider.get(record['doi']) url = current_app.config['CDS_RECORDS_UI_LINKS_FORMAT'].format( recid=pid_value) doc = datacite_v31.serialize(dcp.pid, record) if dcp.pid.status == PIDStatus.REGISTERED: dcp.update(url, doc) else: dcp.register(url, doc) db.session.commit() except Exception as exc: db.session.rollback() raise self.retry(max_retries=max_retries, countdown=countdown, exc=exc)
def datacite_register(pid_value, record_uuid): """Mint the DOI with DataCite. :param pid_value: Value of record PID, with pid_type='recid'. :type pid_value: str """ try: record = Record.get_record(record_uuid) # Bail out if not a Zenodo DOI. if not is_local_doi(record['doi']): return dcp = DataCiteProvider.get(record['doi']) url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=pid_value) doc = datacite_v31.serialize(dcp.pid, record) if dcp.pid.status == PIDStatus.REGISTERED: dcp.update(url, doc) else: dcp.register(url, doc) db.session.commit() except Exception as exc: datacite_register.retry(exc=exc)
def check_record_doi(record, update=False): """ Checks that the DOI of a record is registered.""" recid = record.get('_deposit', {}).get('id') click.secho('checking DOI for record {}'.format(recid)) doi_list = [DataCiteProvider.get(d.get('value')) for d in record['_pid'] if d.get('type') == 'DOI'] for doi in doi_list: if _datacite_doi_reference(doi.pid.pid_value) is None: if doi.pid.status == PIDStatus.REGISTERED: # the doi is not truly registered with datacite click.secho(' {}: not registered with datacite'.format( doi.pid.pid_value)) doi.pid.status = PIDStatus.RESERVED click.secho(' {}: {}'.format(doi.pid.pid_value, doi.pid.status)) if doi.pid.status != PIDStatus.RESERVED: continue # RESERVED but not REGISTERED if update: recid = record.get('_deposit', {}).get('id') url = make_record_url(recid) doc = datacite_v31.serialize(doi.pid, record) _datacite_register_doi(doi, url, doc) db.session.commit() click.secho(' registered just now', fg='green', bold=True) else: click.secho(' not registered', fg='red', bold=True)
def test_datacite_error_delete(logger, app, db): """Test reserve errors.""" with app.app_context(): api = MagicMock() provider = DataCiteProvider.create('10.1234/a', client=api) # DOIs in new state doesn't contact datacite api.metadata_delete.side_effect = DataCiteError assert provider.delete() # Already registered DOIs do contact datacite to delete provider = DataCiteProvider.create('10.1234/b', client=api, status=PIDStatus.REGISTERED) api.metadata_delete.side_effect = DataCiteError pytest.raises(DataCiteError, provider.delete) assert logger.exception.call_args[0][0] == \ "Failed to delete in DataCite"
def datacite_register(pid_value, record_uuid): """Mint DOI and Concept DOI with DataCite. :param pid_value: Value of record PID, with pid_type='recid'. :type pid_value: str :param record_uuid: Record Metadata UUID. :type record_uuid: str """ try: record = Record.get_record(record_uuid) # Bail out if not a Zenodo DOI. if not is_local_doi(record['doi']): return dcp = DataCiteProvider.get(record['doi']) doc = datacite_v41.serialize(dcp.pid, record) url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=pid_value) if dcp.pid.status == PIDStatus.REGISTERED: dcp.update(url, doc) else: dcp.register(url, doc) # If this is the latest record version, update/register the Concept DOI # using the metadata of the record. recid = PersistentIdentifier.get('recid', str(record['recid'])) pv = PIDVersioning(child=recid) conceptdoi = record.get('conceptdoi') if conceptdoi and pv.exists and pv.is_last_child: conceptrecid = record.get('conceptrecid') concept_dcp = DataCiteProvider.get(conceptdoi) url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=conceptrecid) doc = datacite_v41.serialize(concept_dcp.pid, record) if concept_dcp.pid.status == PIDStatus.REGISTERED: concept_dcp.update(url, doc) else: concept_dcp.register(url, doc) db.session.commit() except Exception as exc: datacite_register.retry(exc=exc)
def test_datacite_create_get(app, db): """Test datacite provider create/get.""" with app.app_context(): provider = DataCiteProvider.create('10.1234/a') assert provider.pid.status == PIDStatus.NEW assert provider.pid.pid_provider == 'datacite' # Crete passing client kwarg to provider object creation provider = DataCiteProvider.create('10.1234/b', client=MagicMock()) assert provider.pid.status == PIDStatus.NEW assert provider.pid.pid_provider == 'datacite' assert isinstance(provider.api, MagicMock) provider = DataCiteProvider.get('10.1234/a') assert provider.pid.status == PIDStatus.NEW assert provider.pid.pid_provider == 'datacite' provider = DataCiteProvider.get('10.1234/a', client=MagicMock()) assert isinstance(provider.api, MagicMock)
def test_datacite_error_reserve(logger, app, db): """Test reserve errors.""" with app.app_context(): api = MagicMock() provider = DataCiteProvider.create('10.1234/a', client=api) api.metadata_post.side_effect = DataCiteError pytest.raises(DataCiteError, provider.reserve, "testdoc") assert logger.exception.call_args[0][0] == \ "Failed to reserve in DataCite"
def datacite_inactivate(pid_value): """Mint the DOI with DataCite. :param pid_value: Value of record PID, with pid_type='recid'. :type pid_value: str """ try: dcp = DataCiteProvider.get(pid_value) dcp.delete() db.session.commit() except Exception as exc: datacite_inactivate.retry(exc=exc)
def register_doi(doi, url, xml, uuid): """ Given a data submission id, this method takes its assigned DOI, creates the DataCite XML, and registers the DOI. :param data_submissions: :param recid: :return: """ if current_app.config.get('NO_DOI_MINTING', False) or not doi: return None log.info('{0} - {1}'.format(doi, url)) print('Minting doi {}'.format(doi)) try: provider = DataCiteProvider.get(doi, 'doi') except PIDDoesNotExistError: provider = create_doi(doi) pidstore_obj = PersistentIdentifier.query.filter_by(pid_value=doi).first() if pidstore_obj: pidstore_obj.object_uuid = uuid db.session.add(pidstore_obj) db.session.commit() try: provider.register(url, xml) except DataCiteUnauthorizedError: log.error('Unable to mint DOI. No authorisation credentials provided.') except (PIDInvalidAction, IntegrityError): try: provider.update(url, xml) # try again in case of temporary problem except DataCiteError: try: provider.update(url, xml) except DataCiteError as dce: log.error('Error updating {0} for URL {1}\n\n{2}'.format( doi, url, dce)) except DataCiteError: try: provider.register(url, xml) # try again in case of temporary problem except (PIDInvalidAction, IntegrityError): try: provider.update(url, xml) except DataCiteError as dce: log.error('Error updating {0} for URL {1}\n\n{2}'.format( doi, url, dce)) except DataCiteError as dce: log.error('Error registering {0} for URL {1}\n\n{2}'.format( doi, url, dce))
def test_datacite_error_register_update(logger, app): """Test register errors.""" with app.app_context(): api = MagicMock() provider = DataCiteProvider.create('10.1234/a', client=api) api.doi_post.side_effect = DataCiteError pytest.raises(DataCiteError, provider.register, "testurl", "testdoc") assert logger.exception.call_args[0][0] == \ "Failed to register in DataCite" pytest.raises(DataCiteError, provider.update, "testurl", "testdoc") assert logger.exception.call_args[0][0] == \ "Failed to update in DataCite"
def test_datacite_sync(logger, app, db): """Test sync.""" with app.app_context(): api = MagicMock() provider = DataCiteProvider.create('10.1234/a', client=api) assert provider.pid.status == PIDStatus.NEW # Status can be set from api.doi_get reply assert provider.sync_status() assert provider.pid.status == PIDStatus.REGISTERED api.doi_get.assert_called_with(provider.pid.pid_value) api.doi_get.side_effect = DataCiteGoneError assert provider.sync_status() assert provider.pid.status == PIDStatus.DELETED api.doi_get.side_effect = DataCiteNoContentError assert provider.sync_status() assert provider.pid.status == PIDStatus.REGISTERED # Status *cannot/ be set from api.doi_get reply # Try with api.metadata_get api.doi_get.side_effect = DataCiteNotFoundError assert provider.sync_status() assert provider.pid.status == PIDStatus.RESERVED api.metadata_get.assert_called_with(provider.pid.pid_value) api.doi_get.side_effect = DataCiteNotFoundError api.metadata_get.side_effect = DataCiteGoneError assert provider.sync_status() assert provider.pid.status == PIDStatus.DELETED api.doi_get.side_effect = DataCiteNotFoundError api.metadata_get.side_effect = DataCiteNoContentError assert provider.sync_status() assert provider.pid.status == PIDStatus.REGISTERED api.doi_get.side_effect = DataCiteNotFoundError api.metadata_get.side_effect = DataCiteNotFoundError assert provider.sync_status() assert provider.pid.status == PIDStatus.NEW api.doi_get.side_effect = HttpError assert provider.pid.status == PIDStatus.NEW pytest.raises(HttpError, provider.sync_status) assert provider.pid.status == PIDStatus.NEW assert logger.exception.call_args[0][0] == \ "Failed to sync status from DataCite"
def datacite_register(pid_value, record_uuid): """Mint the DOI with DataCite. :param pid_value: Value of record PID, with pid_type='recid'. :type pid_value: str """ record = Record.get_record(record_uuid) dcp = DataCiteProvider.get(record['doi']) url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=pid_value) doc = datacite_v31.serialize(dcp.pid, record) dcp.update(url, doc) if dcp.pid.status == PIDStatus.REGISTERED \ else dcp.register(url, doc) db.session.commit()
def b2share_doi_minter(rec_pid, data, fake_it=False): from invenio_pidstore.models import PIDStatus, PersistentIdentifier from invenio_pidstore.providers.datacite import DataCiteProvider from .serializers import datacite_v31 def select_doi(metadata, status): doi_list = [DataCiteProvider.get(d.get('value')) for d in metadata['_pid'] if d.get('type') == 'DOI'] return [d for d in doi_list if d and d.pid and d.pid.status == status] if select_doi(data, PIDStatus.REGISTERED): return # DOI already allocated url = make_record_url(rec_pid.pid_value) reserved_doi = select_doi(data, PIDStatus.RESERVED) if reserved_doi: doi = reserved_doi[0] else: doi_id = generate_doi(rec_pid.pid_value) doi = DataCiteProvider.create(doi_id, object_type='rec', object_uuid=rec_pid.object_uuid, status=PIDStatus.RESERVED) data['_pid'].append({'value': doi_id, 'type': 'DOI'}) throw_on_failure = current_app.config.get('CFG_FAIL_ON_MISSING_DOI', True) try: doc = datacite_v31.serialize(doi.pid, data) if fake_it: # don't actually register DOI, just pretend to do so doi.pid.register() else: doi.register(url=url, doc=doc) except DataCiteError as e: if throw_on_failure: raise e else: current_app.logger.warning(e)
def b2share_doi_minter(record_uuid, data): from invenio_pidstore.models import PIDStatus, PersistentIdentifier from invenio_pidstore.providers.datacite import DataCiteProvider from .serializers import datacite_v31 def filter_out_reserved_dois(data): ret = [d for d in data['_pid'] if d.get('type') != 'DOI_RESERVED'] data['_pid'] = ret doi_list = [d for d in data['_pid'] if d.get('type') == 'DOI'] if len(doi_list) > 0: return doi = generate_doi(record_uuid) url = make_record_url(record_uuid) filter_out_reserved_dois(data) data['_pid'].append({ 'value': doi, 'type': 'DOI_RESERVED', }) throw_on_failure = current_app.config.get('CFG_FAIL_ON_MISSING_DOI', True) try: dcp = DataCiteProvider.create(doi, object_type='rec', object_uuid=record_uuid, status=PIDStatus.RESERVED) doc = datacite_v31.serialize(dcp.pid, data) dcp.register(url=url, doc=doc) filter_out_reserved_dois(data) data['_pid'].append({ 'value': doi, 'type': 'DOI', }) except DataCiteError as e: if throw_on_failure: raise e else: current_app.logger.warning(e)
def update_datacite_metadata(doi, object_uuid, job_id): """Update DataCite metadata of a single PersistentIdentifier. :param doi: Value of doi PID, with pid_type='doi'. It could be a normal DOI or a concept DOI. :type doi: str :param object_uuid: Record Metadata UUID. :type object_uuid: str :param job_id: id of the job to which this task belongs. :type job_id: str """ task_details = current_cache.get('update_datacite:task_details') if task_details is None or job_id != task_details['job_id']: return record = Record.get_record(object_uuid) dcp = DataCiteProvider.get(doi) if dcp.pid.status != PIDStatus.REGISTERED: return doc = datacite_v41.serialize(dcp.pid, record) for validator in xsd41(): validator.assertValid(etree.XML(doc.encode('utf8'))) url = None if doi == record.get('doi'): url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=str(record['recid'])) elif doi == record.get('conceptdoi'): url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=str(record['conceptrecid'])) result = dcp.update(url, doc) if result is True: dcp.pid.updated = datetime.utcnow() db.session.commit()
def update_datacite_metadata(pid_value, object_uuid, job_id): """Update DataCite metadata of a single PersistentIdentifier.""" task_details = current_cache.get('update_datacite:task_details') if task_details is None or job_id != task_details['job_id']: return record = Record.get_record(object_uuid) dcp = DataCiteProvider.get(record['doi']) doc = datacite_v41.serialize(dcp.pid, record) for validator in xsd41(): validator.assertValid(etree.XML(doc.encode('utf8'))) url = current_app.config['ZENODO_RECORDS_UI_LINKS_FORMAT'].format( recid=pid_value) result = dcp.update(url, doc) if result is True: dcp.pid.updated = datetime.utcnow() db.session.commit()
def select_doi(metadata, status): doi_list = [DataCiteProvider.get(d.get('value')) for d in metadata['_pid'] if d.get('type') == 'DOI'] return [d for d in doi_list if d and d.pid and d.pid.status == status]