Skip to content

Commit

Permalink
Initial wrapper structure
Browse files Browse the repository at this point in the history
  • Loading branch information
tmorrell committed Aug 16, 2019
1 parent 98b92b0 commit 95c55d4
Show file tree
Hide file tree
Showing 10 changed files with 255 additions and 97 deletions.
8 changes: 8 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,11 @@ Running the test suite is as simple as: ::

pip install -e .[all]
./run-tests.sh

Some tests require a DataCite Test Account.
Set the following environment variables
$DATACITE_USER, $DATACITE_PW, $DATACITE_PREFIX
with your account information for doi.test.datacite.org and
run: ::

./run-tests-pw.sh
3 changes: 2 additions & 1 deletion datacite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from __future__ import absolute_import, print_function

from .client import DataCiteMDSClient
from .rest_client import DataCiteRESTClient
from .version import __version__

__all__ = ('DataCiteMDSClient', '__version__')
__all__ = ('DataCiteMDSClient', 'DataCiteRESTClient','__version__')
5 changes: 5 additions & 0 deletions datacite/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from __future__ import absolute_import, print_function

import json
import ssl

import requests
Expand Down Expand Up @@ -89,6 +90,10 @@ def request(self, url, method='GET', body=None, params=None, headers=None):
self.data = res.content
if not isinstance(body, string_types):
self.data = self.data.decode('utf8')
try:
self.json = json.loads(self.data)
except ValueError as e:
self.json = None
except RequestException as e:
raise HttpError(e)
except ssl.SSLError as e:
Expand Down
165 changes: 100 additions & 65 deletions datacite/rest_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,12 @@ def __init__(self, username=None, password=None, url=None, prefix=None,
self.api_ver = api_ver # Currently not used

if test_mode:
self.api_url = 'https://api.test.datacite.org/'
self.api_url = 'https://doi.test.datacite.org/'
else:
self.api_url = url or 'https://api.datacite.org/'
self.api_url = url or 'https://doi.datacite.org/'
if self.api_url[-1] != '/':
self.api_url = self.api_url + "/"
print(self.api_url)

self.timeout = timeout

Expand Down Expand Up @@ -76,102 +77,136 @@ def doi_get(self, doi):
r = self._request_factory()
r.get("dois/" + doi)
if r.code == 200:
return r.data['attributes']['url']
print(r.json)
return r.json['data']['attributes']['url']
else:
raise DataCiteError.factory(r.code, r.data)

def draft_doi(self, new_doi=None):
"""Create a draft doi.
If new_doi is not provided, DataCite
will automatically create a DOI with a random,
recommended DOI suffix
def check_doi(doi, prefix):
"""Check doi structure.
Check that the doi has a form
12.12345/123 with the prefix defined
"""
headers = {'Content-Type': 'content-type: application/vnd.api+json'}
data = {"data":{"attributes":{"prefix":self.prefix}}}
if new_doi:
#If prefix is in doi
if '/' in new_doi:
split = doi.split('/')
if split[0] != self.prefix:
#Provided a DOI with the wrong prefix
raise ValueError
else:
doi = self.prefix +'/'+new_doi
data = {"data":{"attributes":{"doi":new_doi}}}
# Use \r\n for HTTP client data.
body = "\r\n".join(["doi=%s" % new_doi, "url=%s" % location])
#If prefix is in doi
if '/' in doi:
split = doi.split('/')
if split[0] != prefix:
#Provided a DOI with the wrong prefix
raise ValueError
else:
doi = prefix +'/'+doi
return doi

r = self._request_factory()
r.post("doi", body=body, headers=headers)

def post_doi(self, data):
"""Post a JSON payload to DataCite."""
headers = {'Content-Type': 'content-type: application/vnd.api+json'}
r = self._request_factory()
r.post("dois", body={"data":data}, headers=headers)

if r.code == 201:
return r.data
else:
raise DataCiteError.factory(r.code, r.data)

def doi_post(self, new_doi, location):
"""Mint new DOI.

:param new_doi: DOI name for the new resource.
:param location: URL where the resource is located.
:return: "CREATED" or "HANDLE_ALREADY_EXISTS".
def draft_doi(self, doi=None):
"""Create a draft doi.
A draft DOI can be deleted
If new_doi is not provided, DataCite
will automatically create a DOI with a random,
recommended DOI suffix
"""
headers = {'Content-Type': 'text/plain;charset=UTF-8'}
# Use \r\n for HTTP client data.
body = "\r\n".join(["doi=%s" % new_doi, "url=%s" % location])
data = {"attributes":{"prefix":self.prefix}}
if doi:
doi = self.check_doi(doi,self.prefix)
data = {"attributes":{"doi":doi}}

r = self._request_factory()
r.post("doi", body=body, headers=headers)
return self.post_doi(data)

if r.code == 201:
return r.data
else:
raise DataCiteError.factory(r.code, r.data)
def publish_doi(self, metadata, doi=None):
"""Publish a doi.
def metadata_get(self, doi):
"""Get the XML metadata associated to a DOI name.
This DOI will be public and cannot be deleted
:param doi: DOI name of the resource.
If doi is not provided, DataCite
will automatically create a DOI with a random,
recommended DOI suffix
Metadata should follow the DataCite Metadata Schema:
http://schema.datacite.org/
:param metadata: JSON format of the metadata.
:return:
"""
headers = {'Accept': 'application/xml',
'Accept-Encoding': 'UTF-8'}
data = {"attributes":metadata}
data["attributes"]["prefix"] =self.prefix
data["attributes"]["event"]="publish"
if doi:
doi = self.check_doi(doi)
data["attributes"]["doi"] = doi

return self.post_doi(data)

r = self._request_factory()
r.get("metadata/" + doi, headers=headers)

if r.code == 200:
return r.data
else:
raise DataCiteError.factory(r.code, r.data)
def private_doi(self, metadata, doi=None):
"""Publish a doi in a registered state.
def metadata_post(self, metadata):
"""Set new metadata for an existing DOI.
A DOI generated by this method will
not be found in DataCite Search
This DOI cannot be deleted
If doi is not provided, DataCite
will automatically create a DOI with a random,
recommended DOI suffix
Metadata should follow the DataCite Metadata Schema:
http://schema.datacite.org/
:param metadata: XML format of the metadata.
:return: "CREATED" or "HANDLE_ALREADY_EXISTS"
:param metadata: JSON format of the metadata.
:return:
"""
headers = {'Content-Type': 'application/xml;charset=UTF-8', }
data = {"attributes":metadata}
data["attributes"]["prefix"] =self.prefix
data["attributes"]["event"]="register"
if doi:
doi = self.check_doi(doi)
data["attributes"]["doi"] = doi

r = self._request_factory()
r.post("metadata", body=metadata, headers=headers)
return self.post_doi(data)

if r.code == 201:
return r.data
else:
raise DataCiteError.factory(r.code, r.data)

def metadata_delete(self, doi):
"""Mark as 'inactive' the metadata set of a DOI resource.
def hide_doi(self, doi):
"""Hide a previously registered DOI.
This DOI will no
longer be found in DataCite Search
:param doi: DOI to hide e.g. 10.12345/1.
:return:
"""
data = {"attributes":{"event":"hide"}}
if doi:
doi = self.check_doi(doi)
data["attributes"]["doi"] = doi

return self.post_doi(data)

### VVV TODO
def metadata_get(self, doi):
"""Get the JSON metadata associated to a DOI name.
:param doi: DOI name of the resource.
:return: "OK"
"""
headers = {'Accept': 'application/xml',
'Accept-Encoding': 'UTF-8'}

r = self._request_factory()
r.delete("metadata/" + doi)
r.get("metadata/" + doi, headers=headers)

if r.code == 200:
return r.data
Expand Down
14 changes: 14 additions & 0 deletions run-tests-pw.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# This file is part of DataCite.
#
# Copyright (C) 2015, 2016 CERN.
#
# DataCite is free software; you can redistribute it and/or modify it
# under the terms of the Revised BSD License; see LICENSE file for
# more details.

pydocstyle datacite && \
isort -rc -c -df **/*.py && \
check-manifest --ignore ".travis-*" && \
sphinx-build -qnNW docs docs/_build/html && \
pytest --runpw tests && \
sphinx-build -qnNW -b doctest docs docs/_build/doctest
7 changes: 5 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@
import responses
from lxml import etree


def pytest_addoption(parser):
parser.addoption(
"--runpw", action="store_true", default=False, help="run tests that
require password"
"--runpw", action="store_true", default=False,
help="run tests that require password"
)


def pytest_collection_modifyitems(config, items):
if config.getoption("--runpw"):
# --runslow given in cli: do not skip slow tests
Expand All @@ -34,6 +36,7 @@ def pytest_collection_modifyitems(config, items):
if "pw" in item.keywords:
item.add_marker(skip_pw)


@pytest.fixture
def example_json_file():
"""Load DataCite v3.1 full example JSON."""
Expand Down
68 changes: 68 additions & 0 deletions tests/example/full_rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import os
from datacite import DataCiteRESTClient, schema42

# If you want to generate XML for earlier versions, you need to use either the
# schema31, schema40 or schema41 instead.

data = {
'identifiers': [{
'identifierType': 'DOI',
'identifier': '10.1234/foo.bar',
}],
'creators': [
{'name': 'Smith, John'},
],
'titles': [
{'title': 'Minimal Test Case', }
],
'publisher': 'Invenio Software',
'publicationYear': '2015',
'types': {
'resourceType': 'Dataset',
'resourceTypeGeneral': 'Dataset'
},
'schemaVersion': 'http://datacite.org/schema/kernel-4',
}

# Validate dictionary
assert schema42.validate(data)

# Generate DataCite XML from dictionary.
doc = schema42.tostring(data)

# Initialize the REST client.
username = os.environ["DATACITE_USER"]
password = os.environ["DATACITE_PW"]
prefix = os.environ["DATACITE_PREFIX"]
d = DataCiteRESTClient(
username=username,
password=password,
prefix=prefix,
test_mode=True
)

# Reserve a draft DOI
doi = d.draft_doi()

# Mint new DOI
#d.doi_post('10.5072/test-doi', 'http://example.org/test-doi')

# Get DOI location
#location = d.doi_get(doi)

# Set alternate URL for content type (available through api)
#d.media_post(
# "10.5072/test-doi",
# {"application/json": "http://example.org/test-doi/json/",
# "application/xml": "http://example.org/test-doi/xml/"}
#)

# Get alternate URLs
#mapping = d.media_get("10.5072/test-doi")
#assert mapping["application/json"] == "http://example.org/test-doi/json/"

# Get metadata for DOI
#doc = d.metadata_get("10.5072/test-doi")

# Make DOI inactive
#d.metadata_delete("10.5072/test-doi")

0 comments on commit 95c55d4

Please sign in to comment.