pax_global_header 0000666 0000000 0000000 00000000064 13671736363 0014531 g ustar 00root root 0000000 0000000 52 comment=090b62c04c74d41284f1b3320bf32fa55008d4d0
crossrefapi-1.5.0/ 0000775 0000000 0000000 00000000000 13671736363 0014054 5 ustar 00root root 0000000 0000000 crossrefapi-1.5.0/.gitignore 0000664 0000000 0000000 00000002205 13671736363 0016043 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
env/
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# dotenv
.env
# virtualenv
.venv
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
crossrefapi-1.5.0/.travis.yml 0000664 0000000 0000000 00000000245 13671736363 0016166 0 ustar 00root root 0000000 0000000 language: python
python:
- "3.5"
- "2.7"
before_install:
- pip install --upgrade setuptools pip
install: python setup.py develop
script: python setup.py test crossrefapi-1.5.0/LICENSE 0000664 0000000 0000000 00000002446 13671736363 0015067 0 ustar 00root root 0000000 0000000 BSD 2-Clause License
Copyright (c) 2017, Fabio Batalha
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
crossrefapi-1.5.0/MANIFEST.in 0000664 0000000 0000000 00000000047 13671736363 0015613 0 ustar 00root root 0000000 0000000 recursive-include articlemeta *.thrift
crossrefapi-1.5.0/README.rst 0000664 0000000 0000000 00000065105 13671736363 0015552 0 ustar 00root root 0000000 0000000 -------------------
Crossref API Client
-------------------
Library with functions to iterate through the Crossref API.
.. image:: https://travis-ci.org/fabiobatalha/crossrefapi.svg?branch=master
:target: https://travis-ci.org/fabiobatalha/crossrefapi
--------------
How to Install
--------------
pip install crossrefapi
----------
How to Use
----------
Works
-----
Agency
``````
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: works.agency('10.1590/0102-311x00133115')
Out[3]:
{'DOI': '10.1590/0102-311x00133115',
'agency': {'id': 'crossref', 'label': 'CrossRef'}}
Sample
``````
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: for item in works.sample(2):
...: print(item['title'])
...:
['On the Origin of the Color-Magnitude Relation in the Virgo Cluster']
['Biopsychosocial Wellbeing among Women with Gynaecological Cancer']
Query
`````
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: w1 = works.query(bibliographic='zika', author='johannes', publisher_name='Wiley-Blackwell')
In [4]: for item in w1:
...: print(item['title'])
...:
...:
['Inactivation and removal of Zika virus during manufacture of plasma-derived medicinal products']
['Harmonization of nucleic acid testing for Zika virus: development of the 1st\n World Health Organization International Standard']
Doi
```
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: works.doi('10.1590/0102-311x00133115')
Out[3]:
{'DOI': '10.1590/0102-311x00133115',
'ISSN': ['0102-311X'],
'URL': 'http://dx.doi.org/10.1590/0102-311x00133115',
'alternative-id': ['S0102-311X2016001107002'],
'author': [{'affiliation': [{'name': 'Surin Rajabhat University, Thailand'}],
'family': 'Wiwanitki',
'given': 'Viroj'}],
'container-title': ['Cadernos de Saúde Pública'],
'content-domain': {'crossmark-restriction': False, 'domain': []},
'created': {'date-parts': [[2016, 12, 7]],
'date-time': '2016-12-07T21:52:08Z',
'timestamp': 1481147528000},
'deposited': {'date-parts': [[2017, 5, 24]],
'date-time': '2017-05-24T01:57:26Z',
'timestamp': 1495591046000},
'indexed': {'date-parts': [[2017, 5, 24]],
'date-time': '2017-05-24T22:39:11Z',
'timestamp': 1495665551858},
'is-referenced-by-count': 0,
'issn-type': [{'type': 'electronic', 'value': '0102-311X'}],
'issue': '11',
'issued': {'date-parts': [[2016, 11]]},
'member': '530',
'original-title': [],
'prefix': '10.1590',
'published-print': {'date-parts': [[2016, 11]]},
'publisher': 'FapUNIFESP (SciELO)',
'reference-count': 3,
'references-count': 3,
'relation': {},
'score': 1.0,
'short-container-title': ['Cad. Saúde Pública'],
'short-title': [],
'source': 'Crossref',
'subject': ['Medicine(all)'],
'subtitle': [],
'title': ['Congenital Zika virus syndrome'],
'type': 'journal-article',
'volume': '32'}
Select
``````
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI, prefix'):
...: print(i)
...:
{'DOI': '10.1111/str.12144', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1111'}
{'DOI': '10.1002/admi.201400154', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1002'}
{'DOI': '10.1016/j.surfcoat.2010.10.057', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1007/s10528-015-9707-8', 'member': 'http://id.crossref.org/member/297', 'prefix': '10.1007'}
{'DOI': '10.1016/j.powtec.2016.04.009', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
In [4]: for i in works.filter(has_funder='true', has_license='true').sample(5).select(['DOI', 'prefix']):
...: print(i)
...:
{'DOI': '10.1002/jgrd.50059', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1002'}
{'DOI': '10.1111/ajt.13880', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1111'}
{'DOI': '10.1016/j.apgeochem.2015.05.006', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1016/j.triboint.2015.01.023', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1007/s10854-016-4649-4', 'member': 'http://id.crossref.org/member/297', 'prefix': '10.1007'}
In [5]: for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI').select('prefix'):
...: print(i)
...:
{'DOI': '10.1002/mrm.25790', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1002'}
{'DOI': '10.1016/j.istruc.2016.11.001', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1002/anie.201505015', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1002'}
{'DOI': '10.1016/j.archoralbio.2010.11.011', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1145/3035918.3064012', 'member': 'http://id.crossref.org/member/320', 'prefix': '10.1145'}
In [6]: for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI', 'prefix'):
...: print(i)
...:
{'DOI': '10.1016/j.cplett.2015.11.062', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1016/j.bjp.2015.06.001', 'member': 'http://id.crossref.org/member/78', 'prefix': '10.1016'}
{'DOI': '10.1111/php.12613', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1111'}
{'DOI': '10.1002/cfg.144', 'member': 'http://id.crossref.org/member/98', 'prefix': '10.1155'}
{'DOI': '10.1002/alr.21987', 'member': 'http://id.crossref.org/member/311', 'prefix': '10.1002'}
Facet
`````
.. code-block:: python
In [1]: from crossref.restful import Works, Prefixes
In [2]: works = Works()
In [3]: works.facet('issn', 10)
Out[3]:
{'issn': {'value-count': 10,
'values': {'http://id.crossref.org/issn/0009-2975': 306546,
'http://id.crossref.org/issn/0028-0836': 395353,
'http://id.crossref.org/issn/0140-6736': 458909,
'http://id.crossref.org/issn/0302-9743': 369955,
'http://id.crossref.org/issn/0931-7597': 487523,
'http://id.crossref.org/issn/0959-8138': 392754,
'http://id.crossref.org/issn/1095-9203': 253978,
'http://id.crossref.org/issn/1468-5833': 388355,
'http://id.crossref.org/issn/1556-5068': 273653,
'http://id.crossref.org/issn/1611-3349': 329573}}}
In [4]: prefixes = Prefixes()
In [5]: prefixes.works('10.1590').facet('issn', 10)
Out[5]:
{'issn': {'value-count': 10,
'values': {'http://id.crossref.org/issn/0004-282X': 7712,
'http://id.crossref.org/issn/0034-8910': 4752,
'http://id.crossref.org/issn/0037-8682': 4179,
'http://id.crossref.org/issn/0074-0276': 7941,
'http://id.crossref.org/issn/0100-204X': 3946,
'http://id.crossref.org/issn/0100-4042': 4198,
'http://id.crossref.org/issn/0102-311X': 6548,
'http://id.crossref.org/issn/0103-8478': 6607,
'http://id.crossref.org/issn/1413-8123': 4658,
'http://id.crossref.org/issn/1516-3598': 4678}}}
In [6]: prefixes.works('10.1590').query('zika').facet('issn', 10)
Out[6]:
{'issn': {'value-count': 10,
'values': {'http://id.crossref.org/issn/0004-282X': 4,
'http://id.crossref.org/issn/0036-4665': 4,
'http://id.crossref.org/issn/0037-8682': 7,
'http://id.crossref.org/issn/0074-0276': 7,
'http://id.crossref.org/issn/0102-311X': 12,
'http://id.crossref.org/issn/0103-7331': 2,
'http://id.crossref.org/issn/0104-4230': 3,
'http://id.crossref.org/issn/1519-3829': 7,
'http://id.crossref.org/issn/1679-4508': 2,
'http://id.crossref.org/issn/1806-8324': 2}}}
Journals
--------
Exemplifying the use of API Library to retrieve data from Journals endpoint.
.. code-block:: python
In [1]: from crossref.restful import Journals
In [2]: journals = Journals()
In [3]: journals.journal('0102-311X')
Out[3]:
{'ISSN': ['0102-311X', '0102-311X'],
'breakdowns': {'dois-by-issued-year': [[2013, 462],
[2007, 433],
[2008, 416],
[2009, 347],
[2006, 344],
[2014, 292],
[2004, 275],
[2012, 273],
[2011, 270],
[2010, 270],
[2005, 264],
[2003, 257],
[2001, 220],
[2002, 219],
[1998, 187],
[2000, 169],
[1997, 142],
[1999, 136],
[1994, 110],
[1995, 104],
[1996, 103],
[1993, 99],
[2015, 93],
[1992, 65],
[1986, 63],
[1985, 53],
[1990, 49],
[1988, 49],
[1991, 48],
[1987, 46],
[1989, 45]]},
'counts': {'backfile-dois': 5565, 'current-dois': 335, 'total-dois': 5900},
'coverage': {'award-numbers-backfile': 0.0,
'award-numbers-current': 0.0,
'funders-backfile': 0.0,
'funders-current': 0.0,
'licenses-backfile': 0.0,
'licenses-current': 0.0,
'orcids-backfile': 0.0,
'orcids-current': 0.0,
'references-backfile': 0.0,
'references-current': 0.0,
'resource-links-backfile': 0.0,
'resource-links-current': 0.0,
'update-policies-backfile': 0.0,
'update-policies-current': 0.0},
'flags': {'deposits': True,
'deposits-articles': True,
'deposits-award-numbers-backfile': False,
'deposits-award-numbers-current': False,
'deposits-funders-backfile': False,
'deposits-funders-current': False,
'deposits-licenses-backfile': False,
'deposits-licenses-current': False,
'deposits-orcids-backfile': False,
'deposits-orcids-current': False,
'deposits-references-backfile': False,
'deposits-references-current': False,
'deposits-resource-links-backfile': False,
'deposits-resource-links-current': False,
'deposits-update-policies-backfile': False,
'deposits-update-policies-current': False},
'last-status-check-time': 1459491023622,
'publisher': 'SciELO',
'title': 'Cadernos de Saúde Pública'}
In [4]: journals.journal_exists('0102-311X')
Out[4]: True
In [5]: journals.query('Cadernos').url
Out[5]: 'https://api.crossref.org/journals?query=Cadernos'
In [6]: journals.query('Cadernos').count()
Out[6]: 60
In [7]: journals.works('0102-311X').query('zika').url
Out[7]: 'https://api.crossref.org/journals/0102-311X/works?query=zika'
In [8]: journals.works('0102-311X').query('zika').count()
Out[8]: 12
In [9]: journals.works('0102-311X').query('zika').query(author='Diniz').url
Out[9]: 'https://api.crossref.org/journals/0102-311X/works?query.author=Diniz&query=zika'
In [10]: journals.works('0102-311X').query('zika').query(author='Diniz').count()
Out[10]: 1
Base Methods
------------
The base methods could be used compounded with with query, filter, sort, order and facet methods.
Version
```````
This method returns the Crossref API version.
.. code-block:: python
In [1]: from crossref.restful import Journals
In [2]: journals = Journals()
In [3]: journals.version
Out[3]: '1.0.0'
Count
`````
This method returns the total of itens a query result should retrive. This method will not
iterate and retrieve through the API documents. This method will fetch 0 documents
and retrieve the value of **total-result** attribute.
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: works.query('zika').count()
Out[3]: 3597
In [4]: works.query('zika').filter(from_online_pub_date='2017').count()
Out[4]: 444
Url
```
This method returns the url that will be used to query the Crossref API.
.. code-block:: python
In [1]: from crossref.restful import Works
In [2]: works = Works()
In [3]: works.query('zika').url
Out[3]: 'https://api.crossref.org/works?query=zika'
In [4]: works.query('zika').filter(from_online_pub_date='2017').url
Out[4]: 'https://api.crossref.org/works?query=zika&filter=from-online-pub-date%3A2017'
In [5]: works.query('zika').filter(from_online_pub_date='2017').query(author='Mari').url
Out[5]: 'https://api.crossref.org/works?query.author=Mari&filter=from-online-pub-date%3A2017&query=zika'
In [6]: works.query('zika').filter(from_online_pub_date='2017').query(author='Mari').sort('published').url
Out[6]: 'https://api.crossref.org/works?query.author=Mari&query=zika&filter=from-online-pub-date%3A2017&sort=published'
In [7]: works.query('zika').filter(from_online_pub_date='2017').query(author='Mari').sort('published').order('asc').url
Out[7]: 'https://api.crossref.org/works?filter=from-online-pub-date%3A2017&query.author=Mari&order=asc&query=zika&sort=published'
In [8]: from crossref.restful import Prefixes
In [9]: prefixes = Prefixes()
In [10]: prefixes.works('10.1590').query('zike').url
Out[10]: 'https://api.crossref.org/prefixes/10.1590/works?query=zike'
In [11]: from crossref.restful import Journals
In [12]: journals = Journals()
In [13]: journals.url
Out[13]: 'https://api.crossref.org/journals'
In [14]: journals.works('0102-311X').url
Out[14]: 'https://api.crossref.org/journals/0102-311X/works'
In [15]: journals.works('0102-311X').query('zika').url
Out[15]: 'https://api.crossref.org/journals/0102-311X/works?query=zika'
In [16]: journals.works('0102-311X').query('zika').count()
Out[16]: 12
All
```
This method returns all items of an endpoint. It will use the limit offset
parameters to iterate through the endpoints Journals, Types, Members and Prefixes.
For the **works** endpoint, the library will make use of the **cursor** to paginate through
API until it is totally consumed.
.. code-block:: python
In [1]: from crossref.restful import Journals
In [2]: journals = Journals()
In [3]: for item in journals.all():
...: print(item['title'])
...:
JNSM
New Comprehensive Biochemistry
New Frontiers in Ophthalmology
Oral Health Case Reports
Orbit A Journal of American Literature
ORDO
Support for Polite Requests (Etiquette)
---------------------------------------
Respecting the Crossref API polices for polite requests. This library allows users
to setup an Etiquette object to be used in the http requests.
.. code-block:: python
In [1]: from crossref.restful import Works, Etiquette
In [2]: my_etiquette = Etiquette('My Project Name', 'My Project version', 'My Project URL', 'My contact email')
In [3]: str(my_etiquette)
Out[3]: 'My Project Name/My Project version (My Project URL; mailto:My contact email) BasedOn: CrossrefAPI/1.1.0'
In [4]: my_etiquette = Etiquette('My Project Name', '0.2alpha', 'https://myalphaproject.com', 'anonymous@myalphaproject.com')
In [5]: str(my_etiquette)
Out[5]: 'My Project Name/0.2alpha (https://myalphaproject.com; mailto:anonymous@myalphaproject.com) BasedOn: CrossrefAPI/1.1.0'
In [6]: works = Works(etiquette=my_etiquette)
In [7]: for i in works.sample(5).select('DOI'):
...: print(i)
...:
{'DOI': '10.1016/j.ceramint.2014.10.086'}
{'DOI': '10.1016/j.biomaterials.2012.02.034'}
{'DOI': '10.1001/jamaoto.2013.6450'}
{'DOI': '10.1016/s0021-9290(17)30138-0'}
{'DOI': '10.1109/ancs.2011.11'}
Voilá!!! The requests made for the Crossref API, were made setting the user-agent as: 'My Project Name/0.2alpha (https://myalphaproject.com; mailto:anonymous@myalphaproject.com) BasedOn: CrossrefAPI/1.1.0'
Depositing Metadata to Crossref
-------------------------------
This library implements the deposit operation "doMDUpload", it means you are able to submit Digital Objects Metadata to Crossref. Se more are: https://support.crossref.org/hc/en-us/articles/214960123
To do that, you must have an active publisher account with crossref.org.
First of all, you need a valid XML following the crossref DTD.
.. code-block:: xml
c5473e12dc8e4f36a40f76f8eae15280
20171009132847
SciELO
crossref@scielo.org
SciELO
Revista Brasileira de Ciência Avícola
Rev. Bras. Cienc. Avic.
1516-635X
09
2017
19
3
Climatic Variation: Effects on Stress Levels, Feed Intake, and Bodyweight of Broilers
R
Osti
Huazhong Agricultural University, China
D
Bhattarai
Huazhong Agricultural University, China
D
Zhou
Huazhong Agricultural University, China
09
2017
489
496
S1516-635X2017000300489
10.1590/1806-9061-2017-0494
http://www.scielo.br/scielo.php?script=sci_arttext&pid=S1516-635X2017000300489&lng=en&tlng=en
Journal of Agriculture Science
Alade O
5
176
2013
Perceived effect of climate variation on poultry production in Oke Ogun area of Oyo State
...
Poultry Science
Zulkifli I
88
471
2009
Crating and heat stress influence blood parameters and heat shock protein 70 expression in broiler chickens showing short or long tonic immobility reactions
Second! Using the library
.. code-block:: python
In [1]: from crossref.restful import Depositor
In [2]: request_xml = open('tests/fixtures/deposit_xml_sample.xml', 'r').read()
In [3]: depositor = Depositor('your prefix', 'your crossref user', 'your crossref password')
In [4]: response = depositor.register_doi('testing_20171011', request_xml)
In [5]: response.status_code
Out[5]: 200
In [6]: response.text
Out[6]: '\n\n\n\n\n
SUCCESS\n\n\nSUCCESS
\nYour batch submission was successfully received.
\n\n\n'
In [7]: response = depositor.request_doi_status_by_filename('testing_20171011.xml')
In [8]: response.text
Out[8]: '\n\r\n 1415653976\r\n \r\n'
In [9]: response = depositor.request_doi_status_by_filename('testing_20171011.xml')
In [10]: response.text
Out[10]: '\n\r\n 1415653976\r\n \r\n'
In [11]: response = depositor.request_doi_status_by_filename('testing_20171011.xml', data_type='result')
In [12]: response.text
Out[12]: '\n\r\n 1415653976\r\n \r\n'
In [13]: response = depositor.request_doi_status_by_filename('testing_20171011.xml', data_type='contents')
In [14]: response.text
Out[14]: '\n\n \n c5473e12dc8e4f36a40f76f8eae15280\n 20171009132847\n \n SciELO\n crossref@scielo.org\n \n SciELO\n \n \n \n \n Revista Brasileira de Ciência Avícola\n Rev. Bras. Cienc. Avic.\n 1516-635X\n \n \n \n 09\n 2017\n \n \n 19\n \n 3\n \n \n \n Climatic Variation: Effects on Stress Levels, Feed Intake, and Bodyweight of Broilers\n \n \n \n R\n Osti\n Huazhong Agricultural University, China\n \n \n D\n Bhattarai\n Huazhong Agricultural University, China\n \n \n D\n Zhou\n Huazhong Agricultural University, China\n \n \n \n 09\n 2017\n \n \n 489\n 496\n \n \n S1516-635X2017000300489\n \n'
In [15]: response = depositor.request_doi_status_by_filename('testing_20171011.xml', data_type='result')
In [16]: response.text
Out[16]:
1415649102
9112073c7f474394adc01b82e27ea2a8
10.1590/0037-8682-0216-2016
Successfully updated
10.1590/0037-8682-0284-2014
10.1371/journal.pone.0090237
10.1093/infdis/172.6.1561
10.1016/j.ijpara.2011.01.005
10.1016/j.rvsc.2013.01.006
10.1093/trstmh/tru113
10.1590/0074-02760150459
1
1
0
0
Explaining the code
```````````````````
**Line 1:** Importing the Depositor Class
**Line 2:** Loading a valid XML for deposit
**Line 3:** Creating an instance of Depositor. You should use you crossref credentials at this point. If you wana be polite, you should also give an etiquette object at this momment.
.. block-code:: python
etiquette = Etiquette('My Project Name', 'My Project version', 'My Project URL', 'My contact email')
Depositor('your prefix', 'your crossref user', 'your crossref password', etiquette)
**Line 4:** Requesting the DOI (Id do not means you DOI was registered, it is just a DOI Request)
**Line 5:** Checking the DOI request response.
**Line 6:** Printing the DOI request response body.
**Line 7:** Requesting the DOI registering status.
**Line 8:** Checking the DOI registering status, reading the body of the response. You should parse this XML to have the current status of the DOI registering request. You should do this util have an success or error status retrieved.
**Line 9-12:** Rechecking the request status. It is still in queue. You can also set the response type between ['result', 'contents'], where result will retrieve the status of the DOI registering process, and contents will retrieve the submitted XML content while requesting the DOI.
**Line 13-14:** Checking the content submitted passing the attribute data_type='contents'.
**Line 15-16:** After a while, the success status was received.
crossrefapi-1.5.0/crossref/ 0000775 0000000 0000000 00000000000 13671736363 0015702 5 ustar 00root root 0000000 0000000 crossrefapi-1.5.0/crossref/__init__.py 0000664 0000000 0000000 00000000022 13671736363 0020005 0 ustar 00root root 0000000 0000000 VERSION = '1.4.0'
crossrefapi-1.5.0/crossref/restful.py 0000664 0000000 0000000 00000201134 13671736363 0017741 0 ustar 00root root 0000000 0000000 # coding: utf-8
import requests
import json
from time import sleep
from datetime import datetime, timedelta
from crossref import validators, VERSION
LIMIT = 100
MAXOFFSET = 10000
FACETS_MAX_LIMIT = 1000
API = "api.crossref.org"
class CrossrefAPIError(Exception):
pass
class MaxOffsetError(CrossrefAPIError):
pass
class UrlSyntaxError(CrossrefAPIError, ValueError):
pass
class HTTPRequest(object):
THROTTLING_TUNNING_TIME = 600
def __init__(self, throttle=True):
self.throttle = throttle
self.rate_limits = {
'X-Rate-Limit-Limit': 50,
'X-Rate-Limit-Interval': 1
}
def _update_rate_limits(self, headers):
self.rate_limits['X-Rate-Limit-Limit'] = int(headers.get('X-Rate-Limit-Limit', 50))
interval_value = int(headers.get('X-Rate-Limit-Interval', '1s')[:-1])
interval_scope = headers.get('X-Rate-Limit-Interval', '1s')[-1]
if interval_scope == 'm':
interval_value = interval_value * 60
if interval_scope == 'h':
interval_value = interval_value * 60 * 60
self.rate_limits['X-Rate-Limit-Interval'] = interval_value
@property
def throttling_time(self):
return self.rate_limits['X-Rate-Limit-Interval'] / self.rate_limits['X-Rate-Limit-Limit']
def do_http_request(self, method, endpoint, data=None, files=None, timeout=100, only_headers=False, custom_header=None):
if only_headers is True:
return requests.head(endpoint)
if method == 'post':
action = requests.post
else:
action = requests.get
if custom_header:
headers = custom_header
else:
headers = {'user-agent': str(Etiquette())}
if method == 'post':
result = action(endpoint, data=data, files=files, timeout=timeout, headers=headers)
else:
result = action(endpoint, params=data, timeout=timeout, headers=headers)
if self.throttle is True:
self._update_rate_limits(result.headers)
sleep(self.throttling_time)
return result
def build_url_endpoint(endpoint, context=None):
endpoint = '/'.join([i for i in [context, endpoint] if i])
return 'https://%s/%s' % (API, endpoint)
class Etiquette:
def __init__(self, application_name='undefined', application_version='undefined', application_url='undefined', contact_email='anonymous'):
self.application_name = application_name
self.application_version = application_version
self.application_url = application_url
self.contact_email = contact_email
def __str__(self):
return '%s/%s (%s; mailto:%s) BasedOn: CrossrefAPI/%s' % (
self.application_name,
self.application_version,
self.application_url,
self.contact_email,
VERSION
)
class Endpoint:
CURSOR_AS_ITER_METHOD = False
def __init__(self, request_url=None, request_params=None, context=None, etiquette=None, throttle=True, crossref_plus_token=None):
self.do_http_request = HTTPRequest(throttle=throttle).do_http_request
self.etiquette = etiquette or Etiquette()
self.custom_header = {'user-agent': str(self.etiquette)}
self.crossref_plus_token = crossref_plus_token
if crossref_plus_token:
self.custom_header["Crossref-Plus-API-Token"] = self.crossref_plus_token
self.request_url = request_url or build_url_endpoint(self.ENDPOINT, context)
self.request_params = request_params or dict()
self.context = context or ''
@property
def _rate_limits(self):
request_params = dict(self.request_params)
request_url = str(self.request_url)
result = self.do_http_request(
'get',
request_url,
only_headers=True,
custom_header=self.custom_header,
throttle=False
)
rate_limits = {
'X-Rate-Limit-Limit': result.headers.get('X-Rate-Limit-Limit', 'undefined'),
'X-Rate-Limit-Interval': result.headers.get('X-Rate-Limit-Interval', 'undefined')
}
return rate_limits
def _escaped_pagging(self):
escape_pagging = ['offset', 'rows']
request_params = dict(self.request_params)
for item in escape_pagging:
try:
del(request_params[item])
except KeyError:
pass
return request_params
@property
def version(self):
"""
This attribute retrieve the API version.
>>> Works().version
'1.0.0'
"""
request_params = dict(self.request_params)
request_url = str(self.request_url)
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
).json()
return result['message-version']
@property
def x_rate_limit_limit(self):
return self._rate_limits.get('X-Rate-Limit-Limit', 'undefined')
@property
def x_rate_limit_interval(self):
return self._rate_limits.get('X-Rate-Limit-Interval', 'undefined')
def count(self):
"""
This method retrieve the total of records resulting from a given query.
This attribute can be used compounded with query, filter,
sort, order and facet methods.
Examples:
>>> from crossref.restful import Works
>>> Works().query('zika').count()
3597
>>> Works().query('zika').filter(prefix='10.1590').count()
61
>>> Works().query('zika').filter(prefix='10.1590').sort('published').order('desc').filter(has_abstract='true').count()
14
>>> Works().query('zika').filter(prefix='10.1590').sort('published').order('desc').filter(has_abstract='true').query(author='Marli').count()
1
"""
request_params = dict(self.request_params)
request_url = str(self.request_url)
request_params['rows'] = 0
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
).json()
return int(result['message']['total-results'])
@property
def url(self):
"""
This attribute retrieve the url that will be used as a HTTP request to
the Crossref API.
This attribute can be used compounded with query, filter,
sort, order and facet methods.
Examples:
>>> from crossref.restful import Works
>>> Works().query('zika').url
'https://api.crossref.org/works?query=zika'
>>> Works().query('zika').filter(prefix='10.1590').url
'https://api.crossref.org/works?query=zika&filter=prefix%3A10.1590'
>>> Works().query('zika').filter(prefix='10.1590').sort('published').order('desc').url
'https://api.crossref.org/works?sort=published&order=desc&query=zika&filter=prefix%3A10.1590'
>>> Works().query('zika').filter(prefix='10.1590').sort('published').order('desc').filter(has_abstract='true').query(author='Marli').url
'https://api.crossref.org/works?sort=published&filter=prefix%3A10.1590%2Chas-abstract%3Atrue&query=zika&order=desc&query.author=Marli'
"""
request_params = self._escaped_pagging()
sorted_request_params = sorted([(k, v) for k, v in request_params.items()])
req = requests.Request(
'get', self.request_url, params=sorted_request_params).prepare()
return req.url
def all(self):
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = {}
return iter(self.__class__(request_url, request_params, context, etiquette=self.etiquette, crossref_plus_token=self.crossref_plus_token))
def __iter__(self):
request_url = str(self.request_url)
if 'sample' in self.request_params:
request_params = self._escaped_pagging()
result = self.do_http_request(
'get',
self.request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
raise StopIteration()
result = result.json()
for item in result['message']['items']:
yield item
return
if self.CURSOR_AS_ITER_METHOD is True:
request_params = dict(self.request_params)
request_params['cursor'] = '*'
request_params['rows'] = LIMIT
while True:
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
raise StopIteration()
result = result.json()
if len(result['message']['items']) == 0:
return
for item in result['message']['items']:
yield item
request_params['cursor'] = result['message']['next-cursor']
else:
request_params = dict(self.request_params)
request_params['offset'] = 0
request_params['rows'] = LIMIT
while True:
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
raise StopIteration()
result = result.json()
if len(result['message']['items']) == 0:
return
for item in result['message']['items']:
yield item
request_params['offset'] += LIMIT + 1
if request_params['offset'] >= MAXOFFSET:
raise MaxOffsetError(
'Offset exceded the max offset of %d',
MAXOFFSET
)
class Works(Endpoint):
CURSOR_AS_ITER_METHOD = True
ENDPOINT = 'works'
ORDER_VALUES = ('asc', 'desc', '1', '-1')
SORT_VALUES = (
'created',
'deposited',
'indexed',
'is-referenced-by-count',
'issued',
'published',
'published-online',
'published-print',
'references-count',
'relevance',
'score',
'submitted',
'updated'
)
FIELDS_QUERY = (
'affiliation',
'author',
'bibliographic',
'chair',
'container_title',
'contributor',
'editor',
'event_acronym',
'event_location',
'event_name',
'event_sponsor',
'event_theme',
'funder_name',
'publisher_location',
'publisher_name',
'translator'
)
FIELDS_SELECT = (
'abstract',
'URL',
'member',
'posted',
'score',
'created',
'degree',
'update-policy',
'short-title',
'license',
'ISSN',
'container-title',
'issued',
'update-to',
'issue',
'prefix',
'approved',
'indexed',
'article-number',
'clinical-trial-number',
'accepted',
'author',
'group-title',
'DOI',
'is-referenced-by-count',
'updated-by',
'event',
'chair',
'standards-body',
'original-title',
'funder',
'translator',
'archive',
'published-print',
'alternative-id',
'subject',
'subtitle',
'published-online',
'publisher-location',
'content-domain',
'reference',
'title',
'link',
'type',
'publisher',
'volume',
'references-count',
'ISBN',
'issn-type',
'assertion',
'deposited',
'page',
'content-created',
'short-container-title',
'relation',
'editor'
)
FILTER_VALIDATOR = {
'alternative_id': None,
'archive': validators.archive,
'article_number': None,
'assertion': None,
'assertion-group': None,
'award.funder': None,
'award.number': None,
'category-name': None,
'clinical-trial-number': None,
'container-title': None,
'content-domain': None,
'directory': validators.directory,
'doi': None,
'from-accepted-date': validators.is_date,
'from-created-date': validators.is_date,
'from-deposit-date': validators.is_date,
'from-event-end-date': validators.is_date,
'from-event-start-date': validators.is_date,
'from-index-date': validators.is_date,
'from-issued-date': validators.is_date,
'from-online-pub-date': validators.is_date,
'from-posted-date': validators.is_date,
'from-print-pub-date': validators.is_date,
'from-pub-date': validators.is_date,
'from-update-date': validators.is_date,
'full-text.application': None,
'full-text.type': None,
'full-text.version': None,
'funder': None,
'funder-doi-asserted-by': None,
'group-title': None,
'has-abstract': validators.is_bool,
'has-affiliation': validators.is_bool,
'has-archive': validators.is_bool,
'has-assertion': validators.is_bool,
'has-authenticated-orcid': validators.is_bool,
'has-award': validators.is_bool,
'has-clinical-trial-number': validators.is_bool,
'has-content-domain': validators.is_bool,
'has-domain-restriction': validators.is_bool,
'has-event': validators.is_bool,
'has-full-text': validators.is_bool,
'has-funder': validators.is_bool,
'has-funder-doi': validators.is_bool,
'has-license': validators.is_bool,
'has-orcid': validators.is_bool,
'has-references': validators.is_bool,
'has-relation': validators.is_bool,
'has-update': validators.is_bool,
'has-update-policy': validators.is_bool,
'is-update': validators.is_bool,
'isbn': None,
'issn': None,
'license.delay': validators.is_integer,
'license.url': None,
'license.version': None,
'location': None,
'member': validators.is_integer,
'orcid': None,
'prefix': None,
'relation.object': None,
'relation.object-type': None,
'relation.type': None,
'type': validators.document_type,
'type-name': None,
'until-accepted-date': validators.is_date,
'until-created-date': validators.is_date,
'until-deposit-date': validators.is_date,
'until-event-end-date': validators.is_date,
'until-event-start-date': validators.is_date,
'until-index-date': validators.is_date,
'until-issued-date': validators.is_date,
'until-online-pub-date': validators.is_date,
'until-posted-date': validators.is_date,
'until-print-pub-date': validators.is_date,
'until-pub-date': validators.is_date,
'until-update-date': validators.is_date,
'update-type': None,
'updates': None
}
FACET_VALUES = {
'archive': None,
'affiliation': None,
'assertion': None,
'assertion-group': None,
'category-name': None,
'container-title': 1000,
'license': None,
'funder-doi': None,
'funder-name': None,
'issn': 1000,
'orcid': 1000,
'published': None,
'publisher-name': None,
'relation-type': None,
'source': None,
'type-name': None,
'update-type': None
}
def order(self, order='asc'):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded with query, filter,
sort and facet methods.
kwargs: valid SORT_VALUES arguments.
return: iterable object of Works metadata
Example 1:
>>> from crossref.restful import Works
>>> works.query('zika').sort('deposited').order('asc').url
'https://api.crossref.org/works?sort=deposited&query=zika&order=asc'
>>> query = works.query('zika').sort('deposited').order('asc')
>>> for item in query:
... print(item['title'], item['deposited']['date-time'])
...
['A Facile Preparation of 1-(6-Hydroxyindol-1-yl)-2,2-dimethylpropan-1-one'] 2007-02-13T20:56:13Z
['Contributions to the Flora of the Lake Champlain Valley, New York and Vermont, III'] 2007-02-13T20:56:13Z
['Pilularia americana A. Braun in Klamath County, Oregon'] 2007-02-13T20:56:13Z
...
Example 2:
>>> from crossref.restful import Works
>>> works.query('zika').sort('deposited').order('desc').url
'https://api.crossref.org/works?sort=deposited&query=zika&order=desc'
>>> query = works.query('zika').sort('deposited').order('desc')
>>> for item in query:
... print(item['title'], item['deposited']['date-time'])
...
["Planning for the unexpected: Ebola virus, Zika virus, what's next?"] 2017-05-29T12:55:53Z
['Sensitivity of RT-PCR method in samples shown to be positive for Zika virus by RT-qPCR in vector competence studies'] 2017-05-29T12:53:54Z
['Re-evaluation of routine dengue virus serology in travelers in the era of Zika virus emergence'] 2017-05-29T10:46:11Z
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
if order not in self.ORDER_VALUES:
raise UrlSyntaxError(
'Sort order specified as %s but must be one of: %s' % (
str(order),
', '.join(self.ORDER_VALUES)
)
)
request_params['order'] = order
return self.__class__(request_url, request_params, context, self.etiquette)
def select(self, *args):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded with query, filter,
sort and facet methods.
args: valid FIELDS_SELECT arguments.
return: iterable object of Works metadata
Example 1:
>>> from crossref.restful import Works
>>> works = Works()
>>> for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI, prefix'):
... print(i)
...
{'DOI': '10.1016/j.jdiacomp.2016.06.005', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.mssp.2015.07.076', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1002/slct.201700168', 'prefix': '10.1002', 'member': 'http://id.crossref.org/member/311'}
{'DOI': '10.1016/j.actbio.2017.01.034', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.optcom.2013.11.013', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
...
Example 2:
>>> from crossref.restful import Works
>>> works = Works()
>>> for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI').select('prefix'):
>>> print(i)
...
{'DOI': '10.1016/j.sajb.2016.03.010', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.jneumeth.2009.08.017', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.tetlet.2016.05.058', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1007/s00170-017-0689-z', 'prefix': '10.1007', 'member': 'http://id.crossref.org/member/297'}
{'DOI': '10.1016/j.dsr.2016.03.004', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
...
Example: 3:
>>> from crossref.restful import Works
>>> works = Works()
>>>: for i in works.filter(has_funder='true', has_license='true').sample(5).select(['DOI', 'prefix']):
>>> print(i)
...
{'DOI': '10.1111/zoj.12146', 'prefix': '10.1093', 'member': 'http://id.crossref.org/member/286'}
{'DOI': '10.1016/j.bios.2014.04.018', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.cej.2016.10.011', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.dci.2017.08.001', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.icheatmasstransfer.2016.09.012', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
...
Example: 4:
>>> from crossref.restful import Works
>>> works = Works()
>>>: for i in works.filter(has_funder='true', has_license='true').sample(5).select('DOI', 'prefix'):
>>> print(i)
...
{'DOI': '10.1111/zoj.12146', 'prefix': '10.1093', 'member': 'http://id.crossref.org/member/286'}
{'DOI': '10.1016/j.bios.2014.04.018', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.cej.2016.10.011', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.dci.2017.08.001', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
{'DOI': '10.1016/j.icheatmasstransfer.2016.09.012', 'prefix': '10.1016', 'member': 'http://id.crossref.org/member/78'}
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
select_args = []
invalid_select_args = []
for item in args:
if isinstance(item, list):
select_args += [i.strip() for i in item]
if isinstance(item, str):
select_args += [i.strip() for i in item.split(',')]
invalid_select_args = set(select_args) - set(self.FIELDS_SELECT)
if len(invalid_select_args) != 0:
raise UrlSyntaxError(
'Select field\'s specified as (%s) but must be one of: %s' % (
', '.join(invalid_select_args),
', '.join(self.FIELDS_SELECT)
)
)
request_params['select'] = ','.join(
sorted([i for i in set(request_params.get('select', '').split(',') + select_args) if i])
)
return self.__class__(request_url, request_params, context, self.etiquette)
def sort(self, sort='score'):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded with query, filter,
order and facet methods.
kwargs: valid SORT_VALUES arguments.
return: iterable object of Works metadata
Example 1:
>>> from crossref.restful import Works
>>> works = Works()
>>> query = works.sort('deposited')
>>> for item in query:
... print(item['title'])
...
['Integralidade e transdisciplinaridade em equipes multiprofissionais na saúde coletiva']
['Aprendizagem em grupo operativo de diabetes: uma abordagem etnográfica']
['A rotatividade de enfermeiros e médicos: um impasse na implementação da Estratégia de Saúde da Família']
...
Example 2:
>>> from crossref.restful import Works
>>> works = Works()
>>> query = works.sort('relevance')
>>> for item in query:
... print(item['title'])
...
['Proceedings of the American Physical Society']
['Annual Meeting of the Research Society on Alcoholism']
['Local steroid injections: Comment on the American college of rheumatology guidelines for the management of osteoarthritis of the hip and on the letter by Swezey']
['Intraventricular neurocytoma']
['Mammography accreditation']
['Temporal lobe necrosis in nasopharyngeal carcinoma: Pictorial essay']
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
if sort not in self.SORT_VALUES:
raise UrlSyntaxError(
'Sort field specified as %s but must be one of: %s' % (
str(sort),
', '.join(self.SORT_VALUES)
)
)
request_params['sort'] = sort
return self.__class__(request_url, request_params, context, self.etiquette)
def filter(self, **kwargs):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded and recursively with query, filter,
order, sort and facet methods.
kwargs: valid FILTER_VALIDATOR arguments.
return: iterable object of Works metadata
Example:
>>> from crossref.restful import Works
>>> works = Works()
>>> query = works.filter(has_funder='true', has_license='true')
>>> for item in query:
... print(item['title'])
...
['Design of smiling-face-shaped band-notched UWB antenna']
['Phase I clinical and pharmacokinetic study of PM01183 (a tetrahydroisoquinoline, Lurbinectedin) in combination with gemcitabine in patients with advanced solid tumors']
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
for fltr, value in kwargs.items():
decoded_fltr = fltr.replace('__', '.').replace('_', '-')
if decoded_fltr not in self.FILTER_VALIDATOR.keys():
raise UrlSyntaxError(
'Filter %s specified but there is no such filter for this route. Valid filters for this route are: %s' % (
str(decoded_fltr),
', '.join(self.FILTER_VALIDATOR.keys())
)
)
if self.FILTER_VALIDATOR[decoded_fltr] is not None:
self.FILTER_VALIDATOR[decoded_fltr](str(value))
if 'filter' not in request_params:
request_params['filter'] = decoded_fltr + ':' + str(value)
else:
request_params['filter'] += ',' + decoded_fltr + ':' + str(value)
return self.__class__(request_url, request_params, context, self.etiquette)
def facet(self, facet_name, facet_count=100):
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
request_params['rows'] = 0
if facet_name not in self.FACET_VALUES.keys():
raise UrlSyntaxError('Facet %s specified but there is no such facet for this route. Valid facets for this route are: *, affiliation, funder-name, funder-doi, publisher-name, orcid, container-title, assertion, archive, update-type, issn, published, source, type-name, license, category-name, relation-type, assertion-group' %
str(facet_name),
', '.join(self.FACET_VALUES.keys())
)
facet_count = self.FACET_VALUES[facet_name] if self.FACET_VALUES[facet_name] is not None and self.FACET_VALUES[facet_name] <= facet_count else facet_count
request_params['facet'] = '%s:%s' % (facet_name, facet_count)
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
).json()
return result['message']['facets']
def query(self, *args, **kwargs):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded and recursively with query, filter,
order, sort and facet methods.
args: strings (String)
kwargs: valid FIELDS_QUERY arguments.
return: iterable object of Works metadata
Example:
>>> from crossref.restful import Works
>>> works = Works()
>>> query = works.query('Zika Virus')
>>> query.url
'https://api.crossref.org/works?query=Zika+Virus'
>>> for item in query:
... print(item['title'])
...
['Zika Virus']
['Zika virus disease']
['Zika Virus: Laboratory Diagnosis']
['Spread of Zika virus disease']
['Carditis in Zika Virus Infection']
['Understanding Zika virus']
['Zika Virus: History and Infectology']
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
if args:
request_params['query'] = ' '.join([str(i) for i in args])
for field, value in kwargs.items():
if field not in self.FIELDS_QUERY:
raise UrlSyntaxError(
'Field query %s specified but there is no such field query for this route. Valid field queries for this route are: %s' % (
str(field), ', '.join(self.FIELDS_QUERY)
)
)
request_params['query.%s' % field.replace('_', '-')] = value
return self.__class__(request_url, request_params, context, self.etiquette)
def sample(self, sample_size=20):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
kwargs: sample_size (Integer) between 0 and 100.
return: iterable object of Works metadata
Example:
>>> from crossref.restful import Works
>>> works = Works()
>>> works.sample(2).url
'https://api.crossref.org/works?sample=2'
>>> [i['title'] for i in works.sample(2)]
[['A study on the hemolytic properties ofPrevotella nigrescens'],
['The geometry and the radial breathing mode of carbon nanotubes: beyond the ideal behaviour']]
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
try:
if sample_size > 100:
raise UrlSyntaxError(
'Integer specified as %s but must be a positive integer less than or equal to 100.' % str(sample_size)
)
except TypeError:
raise UrlSyntaxError(
'Integer specified as %s but must be a positive integer less than or equal to 100.' % str(sample_size)
)
request_params['sample'] = sample_size
return self.__class__(request_url, request_params, context, self.etiquette)
def doi(self, doi, only_message=True):
"""
This method retrieve the DOI metadata related to a given DOI
number.
args: Crossref DOI id (String)
return: JSON
Example:
>>> from crossref.restful import Works
>>> works = Works()
>>> works.doi('10.1590/S0004-28032013005000001')
{'is-referenced-by-count': 6, 'reference-count': 216, 'DOI': '10.1590/s0004-28032013005000001',
'subtitle': [], 'issued': {'date-parts': [[2013, 4, 19]]}, 'source': 'Crossref',
'short-container-title': ['Arq. Gastroenterol.'], 'references-count': 216, 'short-title': [],
'deposited': {'timestamp': 1495911725000, 'date-time': '2017-05-27T19:02:05Z',
'date-parts': [[2017, 5, 27]]}, 'ISSN': ['0004-2803'], 'type': 'journal-article',
'URL': 'http://dx.doi.org/10.1590/s0004-28032013005000001',
'indexed': {'timestamp': 1496034748592, 'date-time': '2017-05-29T05:12:28Z',
'date-parts': [[2017, 5, 29]]}, 'content-domain': {'crossmark-restriction': False, 'domain': []},
'created': {'timestamp': 1374613284000, 'date-time': '2013-07-23T21:01:24Z',
'date-parts': [[2013, 7, 23]]}, 'issn-type': [{'value': '0004-2803', 'type': 'electronic'}],
'page': '81-96', 'volume': '50', 'original-title': [], 'subject': ['Gastroenterology'],
'relation': {}, 'container-title': ['Arquivos de Gastroenterologia'], 'member': '530',
'prefix': '10.1590', 'published-print': {'date-parts': [[2013, 4, 19]]},
'title': ['3rd BRAZILIAN CONSENSUS ON Helicobacter pylori'],
'publisher': 'FapUNIFESP (SciELO)', 'alternative-id': ['S0004-28032013000200081'],
'abstract': 'Significant abstract data..... .',
'author': [{'affiliation': [{'name': 'Universidade Federal de Minas Gerais, BRAZIL'}],
'family': 'Coelho', 'given': 'Luiz Gonzaga'}, {'affiliation': [
{'name': 'Universidade Federal do Rio Grande do Sul, Brazil'}], 'family': 'Maguinilk',
'given': 'Ismael'}, {'affiliation': [
{'name': 'Presidente de Honra do Núcleo Brasileiro para Estudo do Helicobacter, Brazil'}],
'family': 'Zaterka', 'given': 'Schlioma'}, {'affiliation': [
{'name': 'Universidade Federal do Piauí, Brasil'}], 'family': 'Parente', 'given': 'José Miguel'},
{'affiliation': [{'name': 'Universidade Federal de Minas Gerais, BRAZIL'}],
'family': 'Passos', 'given': 'Maria do Carmo Friche'}, {'affiliation': [
{'name': 'Universidade de São Paulo, Brasil'}], 'family': 'Moraes-Filho',
'given': 'Joaquim Prado P.'}], 'score': 1.0, 'issue': '2'}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, doi])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def agency(self, doi, only_message=True):
"""
This method retrieve the DOI Agency metadata related to a given DOI
number.
args: Crossref DOI id (String)
return: JSON
Example:
>>> from crossref.restful import Works
>>> works = Works()
>>> works.agency('10.1590/S0004-28032013005000001')
{'DOI': '10.1590/s0004-28032013005000001', 'agency': {'label': 'CrossRef', 'id': 'crossref'}}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, doi, 'agency'])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def doi_exists(self, doi):
"""
This method retrieve a boolean according to the existence of a crossref
DOI number. It returns False if the API results a 404 status code.
args: Crossref DOI id (String)
return: Boolean
Example 1:
>>> from crossref.restful import Works
>>> works = Works()
>>> works.doi_exists('10.1590/S0004-28032013005000001')
True
Example 2:
>>> from crossref.restful import Works
>>> works = Works()
>>> works.doi_exists('10.1590/S0004-28032013005000001_invalid_doi')
False
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, doi])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
only_headers=True,
custom_header=self.custom_header
)
if result.status_code == 404:
return False
return True
class Funders(Endpoint):
CURSOR_AS_ITER_METHOD = False
ENDPOINT = 'funders'
FILTER_VALIDATOR = {
'location': None,
}
def query(self, *args):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
args: Funder ID (Integer)
return: iterable object of Funders metadata
Example:
>>> from crossref.restful import Funders
>>> funders = Funders()
>>> funders.query('ABBEY').url
'https://api.crossref.org/funders?query=ABBEY'
>>> next(iter(funders.query('ABBEY')))
{'alt-names': ['Abbey'], 'location': 'United Kingdom', 'replaced-by': [],
'replaces': [], 'name': 'ABBEY AWARDS', 'id': '501100000314',
'tokens': ['abbey', 'awards', 'abbey'],
'uri': 'http://dx.doi.org/10.13039/501100000314'}
"""
request_url = build_url_endpoint(self.ENDPOINT)
request_params = dict(self.request_params)
if args:
request_params['query'] = ' '.join([str(i) for i in args])
return self.__class__(request_url, request_params, self.etiquette)
def filter(self, **kwargs):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded and recursively with query, filter,
order, sort and facet methods.
kwargs: valid FILTER_VALIDATOR arguments.
return: iterable object of Funders metadata
Example:
>>> from crossref.restful import Funders
>>> funders = Funders()
>>> query = funders.filter(location='Japan')
>>> for item in query:
... print(item['name'], item['location'])
...
(u'Central Research Institute, Fukuoka University', u'Japan')
(u'Tohoku University', u'Japan')
(u'Information-Technology Promotion Agency', u'Japan')
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
for fltr, value in kwargs.items():
decoded_fltr = fltr.replace('__', '.').replace('_', '-')
if decoded_fltr not in self.FILTER_VALIDATOR.keys():
raise UrlSyntaxError(
'Filter %s specified but there is no such filter for this route. Valid filters for this route are: %s' % (
str(decoded_fltr),
', '.join(self.FILTER_VALIDATOR.keys())
)
)
if self.FILTER_VALIDATOR[decoded_fltr] is not None:
self.FILTER_VALIDATOR[decoded_fltr](str(value))
if 'filter' not in request_params:
request_params['filter'] = decoded_fltr + ':' + str(value)
else:
request_params['filter'] += ',' + decoded_fltr + ':' + str(value)
return self.__class__(request_url, request_params, context, self.etiquette)
def funder(self, funder_id, only_message=True):
"""funder
This method retrive a crossref funder metadata related to the
given funder_id.
args: Funder ID (Integer)
Example:
>>> from crossref.restful import Funders
>>> funders = Funders()
>>> funders.funder('501100000314')
{'hierarchy': {'501100000314': {}}, 'alt-names': ['Abbey'],
'work-count': 3, 'replaced-by': [], 'replaces': [],
'hierarchy-names': {'501100000314': 'ABBEY AWARDS'},
'uri': 'http://dx.doi.org/10.13039/501100000314', 'location': 'United Kingdom',
'descendant-work-count': 3, 'descendants': [], 'name': 'ABBEY AWARDS',
'id': '501100000314', 'tokens': ['abbey', 'awards', 'abbey']}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(funder_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def funder_exists(self, funder_id):
"""
This method retrieve a boolean according to the existence of a crossref
funder. It returns False if the API results a 404 status code.
args: Crossref Funder id (Integer)
return: Boolean
Example 1:
>>> from crossref.restful import Funders
>>> funders = Funders()
>>> funders.funder_exists('501100000314')
True
Example 2:
>>> from crossref.restful import Funders
>>> funders = Funders()
>>> funders.funder_exists('999999999999')
False
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(funder_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
only_headers=True,
custom_header=self.custom_header
)
if result.status_code == 404:
return False
return True
def works(self, funder_id):
"""
This method retrieve a iterable of Works of the given funder.
args: Crossref allowed document Types (String)
return: Works()
"""
context = '%s/%s' % (self.ENDPOINT, str(funder_id))
return Works(context=context)
class Members(Endpoint):
CURSOR_AS_ITER_METHOD = False
ENDPOINT = 'members'
FILTER_VALIDATOR = {
'prefix': None,
'has-public-references': validators.is_bool,
'backfile-doi-count': validators.is_integer,
'current-doi-count': validators.is_integer
}
def query(self, *args):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
args: strings (String)
return: iterable object of Members metadata
Example:
>>> from crossref.restful import Members
>>> members = Members().query('Korean Association')
members.query('Korean Association').url
'https://api.crossref.org/journals?query=Public+Health+Health+Science'
>>> next(iter(members.query('Korean Association')))
{'prefix': [{'value': '10.20433', 'public-references': False,
'name': 'The New Korean Philosophical Association'}], 'counts': {'total-dois': 0, 'backfile-dois': 0,
'current-dois': 0}, 'coverage': {'references-backfile': 0, 'references-current': 0,
'abstracts-current': 0, 'update-policies-backfile': 0, 'orcids-current': 0, 'orcids-backfile': 0,
'licenses-current': 0, 'affiliations-backfile': 0, 'licenses-backfile': 0, 'update-policies-current': 0,
'resource-links-current': 0, 'resource-links-backfile': 0, 'award-numbers-backfile': 0,
'abstracts-backfile': 0, 'funders-current': 0, 'funders-backfile': 0, 'affiliations-current': 0,
'award-numbers-current': 0}, 'flags': {'deposits-orcids-backfile': False,
'deposits-references-backfile': False, 'deposits-licenses-current': False, 'deposits': False,
'deposits-abstracts-current': False, 'deposits-award-numbers-current': False, 'deposits-articles': False,
'deposits-resource-links-backfile': False, 'deposits-funders-current': False,
'deposits-award-numbers-backfile': False, 'deposits-references-current': False,
'deposits-abstracts-backfile': False, 'deposits-funders-backfile': False,
'deposits-update-policies-current': False, 'deposits-orcids-current': False,
'deposits-licenses-backfile': False, 'deposits-affiliations-backfile': False,
'deposits-update-policies-backfile': False, 'deposits-resource-links-current': False,
'deposits-affiliations-current': False}, 'names': ['The New Korean Philosophical Association'],
'breakdowns': {'dois-by-issued-year': []}, 'location': 'Dongsin Tower, 4th Floor 5, Mullae-dong 6-ga,
Mullae-dong 6-ga Seoul 150-096 South Korea', 'prefixes': ['10.20433'],
'last-status-check-time': 1496034177684, 'id': 8334, 'tokens': ['the', 'new', 'korean', 'philosophical',
'association'], 'primary-name': 'The New Korean Philosophical Association'}
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT)
request_params = dict(self.request_params)
if args:
request_params['query'] = ' '.join([str(i) for i in args])
return self.__class__(request_url, request_params, context, self.etiquette)
def filter(self, **kwargs):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
This method can be used compounded and recursively with query, filter,
order, sort and facet methods.
kwargs: valid FILTER_VALIDATOR arguments.
return: iterable object of Members metadata
Example:
>>> from crossref.restful import Members
>>> members = Members()
>>> query = members.filter(has_public_references='true')
>>> for item in query:
... print(item['prefix'])
...
[{u'public-references': False, u'name': u'Open Library of Humanities', u'value': u'10.16995'}, {u'public-references': True, u'name': u'Martin Eve', u'value': u'10.7766'}]
[{u'public-references': True, u'name': u'Institute of Business Research', u'value': u'10.24122'}]
...
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT, context)
request_params = dict(self.request_params)
for fltr, value in kwargs.items():
decoded_fltr = fltr.replace('__', '.').replace('_', '-')
if decoded_fltr not in self.FILTER_VALIDATOR.keys():
raise UrlSyntaxError(
'Filter %s specified but there is no such filter for this route. Valid filters for this route are: %s' % (
str(decoded_fltr),
', '.join(self.FILTER_VALIDATOR.keys())
)
)
if self.FILTER_VALIDATOR[decoded_fltr] is not None:
self.FILTER_VALIDATOR[decoded_fltr](str(value))
if 'filter' not in request_params:
request_params['filter'] = decoded_fltr + ':' + str(value)
else:
request_params['filter'] += ',' + decoded_fltr + ':' + str(value)
return self.__class__(request_url, request_params, context, self.etiquette)
def member(self, member_id, only_message=True):
"""
This method retrive a crossref member metadata related to the
given member_id.
args: Member ID (Integer)
Example:
>>> from crossref.restful import Members
>>> members = Members()
>>> members.member(101)
{'prefix': [{'value': '10.1024', 'public-references': False,
'name': 'Hogrefe Publishing Group'}, {'value': '10.1027', 'public-references': False,
'name': 'Hogrefe Publishing Group'}, {'value': '10.1026', 'public-references': False,
'name': 'Hogrefe Publishing Group'}], 'counts': {'total-dois': 35039, 'backfile-dois': 31430,
'current-dois': 3609}, 'coverage': {'references-backfile': 0.3601972758769989,
'references-current': 0.019118869677186012, 'abstracts-current': 0.0,
'update-policies-backfile': 0.0, 'orcids-current': 0.0, 'orcids-backfile': 0.0,
'licenses-current': 0.0, 'affiliations-backfile': 0.05685650557279587,
'licenses-backfile': 0.0, 'update-policies-current': 0.0, 'resource-links-current': 0.0,
'resource-links-backfile': 0.0, 'award-numbers-backfile': 0.0, 'abstracts-backfile': 0.0,
'funders-current': 0.0, 'funders-backfile': 0.0, 'affiliations-current': 0.15710723400115967,
'award-numbers-current': 0.0}, 'flags': {'deposits-orcids-backfile': False,
'deposits-references-backfile': True, 'deposits-licenses-current': False, 'deposits': True,
'deposits-abstracts-current': False, 'deposits-award-numbers-current': False,
'deposits-articles': True, 'deposits-resource-links-backfile': False,
'deposits-funders-current': False, 'deposits-award-numbers-backfile': False,
'deposits-references-current': True, 'deposits-abstracts-backfile': False,
'deposits-funders-backfile': False, 'deposits-update-policies-current': False,
'deposits-orcids-current': False, 'deposits-licenses-backfile': False,
'deposits-affiliations-backfile': True, 'deposits-update-policies-backfile': False,
'deposits-resource-links-current': False, 'deposits-affiliations-current': True},
'names': ['Hogrefe Publishing Group'], 'breakdowns': {'dois-by-issued-year':
[[2003, 2329], [2004, 2264], [2002, 2211], [2005, 2204], [2006, 2158], [2007, 2121], [2016, 1954],
[2008, 1884], [2015, 1838], [2012, 1827], [2013, 1805], [2014, 1796], [2009, 1760], [2010, 1718],
[2011, 1681], [2001, 1479], [2000, 1477], [1999, 1267], [2017, 767], [1997, 164], [1996, 140],
[1998, 138], [1995, 103], [1994, 11], [1993, 11], [0, 1]]},
'location': 'Langgass-Strasse 76 Berne CH-3000 Switzerland', 'prefixes': ['10.1024', '10.1027',
'10.1026'], 'last-status-check-time': 1496034132646, 'id': 101, 'tokens': ['hogrefe', 'publishing',
'group'], 'primary-name': 'Hogrefe Publishing Group'}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(member_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def member_exists(self, member_id):
"""
This method retrieve a boolean according to the existence of a crossref
member. It returns False if the API results a 404 status code.
args: Crossref allowed document Type (String)
return: Boolean
Example 1:
>>> from crossref.restful import Members
>>> members = Members()
>>> members.member_exists(101)
True
Example 2:
>>> from crossref.restful import Members
>>> members = Members()
>>> members.member_exists(88888)
False
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(member_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
only_headers=True,
custom_header=self.custom_header
)
if result.status_code == 404:
return False
return True
def works(self, member_id):
"""
This method retrieve a iterable of Works of the given member.
args: Member ID (Integer)
return: Works()
"""
context = '%s/%s' % (self.ENDPOINT, str(member_id))
return Works(context=context)
class Types(Endpoint):
CURSOR_AS_ITER_METHOD = False
ENDPOINT = 'types'
def type(self, type_id, only_message=True):
"""
This method retrive a crossref document type metadata related to the
given type_id.
args: Crossref allowed document Types (String)
Example:
>>> types.type('journal-article')
{'label': 'Journal Article', 'id': 'journal-article'}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(type_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def all(self):
"""
This method retrieve an iterator with all the available types.
return: iterator of crossref document types
Example:
>>> from crossref.restful import Types
>>> types = Types()
>>> [i for i in types.all()]
[{'label': 'Book Section', 'id': 'book-section'},
{'label': 'Monograph', 'id': 'monograph'},
{'label': 'Report', 'id': 'report'},
{'label': 'Book Track', 'id': 'book-track'},
{'label': 'Journal Article', 'id': 'journal-article'},
{'label': 'Part', 'id': 'book-part'},
...
}]
"""
request_url = build_url_endpoint(self.ENDPOINT, self.context)
request_params = dict(self.request_params)
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
raise StopIteration()
result = result.json()
for item in result['message']['items']:
yield item
def type_exists(self, type_id):
"""
This method retrieve a boolean according to the existence of a crossref
document type. It returns False if the API results a 404 status code.
args: Crossref allowed document Type (String)
return: Boolean
Example 1:
>>> from crossref.restful import Types
>>> types = Types()
>>> types.type_exists('journal-article')
True
Example 2:
>>> from crossref.restful import Types
>>> types = Types()
>>> types.type_exists('unavailable type')
False
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(type_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
only_headers=True,
custom_header=self.custom_header
)
if result.status_code == 404:
return False
return True
def works(self, type_id):
"""
This method retrieve a iterable of Works of the given type.
args: Crossref allowed document Types (String)
return: Works()
"""
context = '%s/%s' % (self.ENDPOINT, str(type_id))
return Works(context=context)
class Prefixes(Endpoint):
CURSOR_AS_ITER_METHOD = False
ENDPOINT = 'prefixes'
def prefix(self, prefix_id, only_message=True):
"""
This method retrieve a json with the given Prefix metadata
args: Crossref Prefix (String)
return: JSON
Example:
>>> from crossref.restful import Prefixes
>>> prefixes = Prefixes()
>>> prefixes.prefix('10.1590')
{'name': 'FapUNIFESP (SciELO)', 'member': 'http://id.crossref.org/member/530',
'prefix': 'http://id.crossref.org/prefix/10.1590'}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(prefix_id)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def works(self, prefix_id):
"""
This method retrieve a iterable of Works of the given prefix.
args: Crossref Prefix (String)
return: Works()
"""
context = '%s/%s' % (self.ENDPOINT, str(prefix_id))
return Works(context=context)
class Journals(Endpoint):
CURSOR_AS_ITER_METHOD = False
ENDPOINT = 'journals'
def query(self, *args):
"""
This method retrieve an iterable object that implements the method
__iter__. The arguments given will compose the parameters in the
request url.
args: strings (String)
return: iterable object of Journals metadata
Example:
>>> from crossref.restful import Journals
>>> journals = Journals().query('Public Health', 'Health Science')
>>> journals.url
'https://api.crossref.org/journals?query=Public+Health+Health+Science'
>>> next(iter(journals))
{'last-status-check-time': None, 'counts': None, 'coverage': None,
'publisher': 'ScopeMed International Medical Journal Managment and Indexing System',
'flags': None, 'breakdowns': None, 'ISSN': ['2320-4664', '2277-338X'],
'title': 'International Journal of Medical Science and Public Health'}
"""
context = str(self.context)
request_url = build_url_endpoint(self.ENDPOINT)
request_params = dict(self.request_params)
if args:
request_params['query'] = ' '.join([str(i) for i in args])
return self.__class__(request_url, request_params, context, self.etiquette)
def journal(self, issn, only_message=True):
"""
This method retrieve a json with the given ISSN metadata
args: Journal ISSN (String)
return: Journal JSON data
Example:
>>> from crossref.restful import Journals
>>> journals = Journals()
>>> journals.journal('2277-338X')
{'last-status-check-time': None, 'counts': None, 'coverage': None,
'publisher': 'ScopeMed International Medical Journal Managment and Indexing System',
'flags': None, 'breakdowns': None, 'ISSN': ['2320-4664', '2277-338X'],
'title': 'International Journal of Medical Science and Public Health'}
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(issn)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
custom_header=self.custom_header
)
if result.status_code == 404:
return
result = result.json()
return result['message'] if only_message is True else result
def journal_exists(self, issn):
"""
This method retrieve a boolean according to the existence of a journal
in the Crossref database. It returns False if the API results a 404
status code.
args: Journal ISSN (String)
return: Boolean
Example 1:
>>> from crossref.restful import Journals
>>> journals = Journals()
>>> journals.journal_exists('2277-338X')
True
Example 2:
>>> from crossref.restful import Journals
>>> journals = Journals()
>>> journals.journal_exists('9999-AAAA')
False
"""
request_url = build_url_endpoint(
'/'.join([self.ENDPOINT, str(issn)])
)
request_params = {}
result = self.do_http_request(
'get',
request_url,
data=request_params,
only_headers=True,
custom_header=self.custom_header
)
if result.status_code == 404:
return False
return True
def works(self, issn):
"""
This method retrieve a iterable of Works of the given journal.
args: Journal ISSN (String)
return: Works()
"""
context = '%s/%s' % (self.ENDPOINT, str(issn))
return Works(context=context)
class Depositor(object):
def __init__(self, prefix, api_user, api_key, etiquette=None,
use_test_server=False):
self.do_http_request = HTTPRequest(throttle=False).do_http_request
self.etiquette = etiquette or Etiquette()
self.custom_header = {'user-agent': str(self.etiquette)}
self.prefix = prefix
self.api_user = api_user
self.api_key = api_key
self.use_test_server = use_test_server
def get_endpoint(self, verb):
subdomain = 'test' if self.use_test_server else 'doi'
return "https://{}.crossref.org/servlet/{}".format(subdomain, verb)
def register_doi(self, submission_id, request_xml):
"""
This method registry a new DOI number in Crossref or update some DOI
metadata.
submission_id: Will be used as the submission file name. The file name
could be used in future requests to retrieve the submission status.
request_xml: The XML with the document metadata. It must be under
compliance with the Crossref Submission Schema.
"""
endpoint = self.get_endpoint('deposit')
files = {
'mdFile': ('%s.xml' % submission_id, request_xml)
}
params = {
'operation': 'doMDUpload',
'login_id': self.api_user,
'login_passwd': self.api_key
}
result = self.do_http_request(
'post',
endpoint,
data=params,
files=files,
timeout=10,
custom_header=self.custom_header
)
return result
def request_doi_status_by_filename(self, file_name, data_type='result'):
"""
This method retrieve the DOI requests status.
file_name: Used as unique ID to identify a deposit.
data_type: [contents, result]
contents - retrieve the XML submited by the publisher
result - retrieve a JSON with the status of the submission
"""
endpoint = self.get_endpoint('submissionDownload')
params = {
'usr': self.api_user,
'pwd': self.api_key,
'file_name': file_name,
'type': data_type
}
result = self.do_http_request(
'get',
endpoint,
data=params,
timeout=10,
custom_header=self.custom_header
)
return result
def request_doi_status_by_batch_id(self, doi_batch_id, data_type='result'):
"""
This method retrieve the DOI requests status.
file_name: Used as unique ID to identify a deposit.
data_type: [contents, result]
contents - retrieve the XML submited by the publisher
result - retrieve a XML with the status of the submission
"""
endpoint = self.get_endpoint('submissionDownload')
params = {
'usr': self.api_user,
'pwd': self.api_key,
'doi_batch_id': doi_batch_id,
'type': data_type
}
result = self.do_http_request(
'get',
endpoint,
data=params,
timeout=10,
custom_header=self.custom_header
)
return result
crossrefapi-1.5.0/crossref/utils.py 0000664 0000000 0000000 00000000666 13671736363 0017424 0 ustar 00root root 0000000 0000000 # coding: utf-8
truthy = frozenset(('t', 'true', 'y', 'yes', 'on', '1'))
def asbool(s):
""" Return the boolean value ``True`` if the case-lowered value of string
input ``s`` is a :term:`truthy string`. If ``s`` is already one of the
boolean values ``True`` or ``False``, return it."""
if s is None:
return False
if isinstance(s, bool):
return s
s = str(s).strip()
return s.lower() in truthy
crossrefapi-1.5.0/crossref/validators.py 0000664 0000000 0000000 00000004660 13671736363 0020432 0 ustar 00root root 0000000 0000000 # coding: utf-8
from crossref.utils import asbool
from datetime import datetime
def directory(value):
expected = (
'DOAJ'
)
if str(value) in expected:
return True
raise ValueError('Directory specified as %s but must be one of: %s' % (
str(value),
', '.join(expected)
)
)
def archive(value):
expected = (
'Portico',
'CLOCKSS',
'DWT'
)
if str(value) in expected:
return True
raise ValueError('Archive specified as %s but must be one of: %s' % (
str(value),
', '.join(expected)
)
)
def document_type(value):
expected = (
'book-section',
'monograph',
'report',
'book-track',
'journal-article',
'book-part',
'other',
'book',
'journal-volume',
'book-set',
'reference-entry',
'proceedings-article',
'journal',
'component',
'book-chapter',
'report-series',
'proceedings',
'standard',
'reference-book',
'posted-content',
'journal-issue',
'dissertation',
'dataset',
'book-series',
'edited-book',
'standard-series'
)
if str(value) in expected:
return True
raise ValueError('Type specified as %s but must be one of: %s' % (
str(value),
', '.join(expected)
)
)
def is_bool(value):
expected = ['t', 'true', '1', 'f', 'false', '0']
if str(value) in expected:
return True
raise ValueError('Boolean specified %s True but must be one of: %s' % (
str(value),
', '.join(expected)
)
)
def is_date(value):
try:
datetime.strptime(value, '%Y')
return True
except ValueError:
try:
datetime.strptime(value, '%Y-%m')
return True
except ValueError:
try:
datetime.strptime(value, '%Y-%m-%d')
return True
except:
pass
raise ValueError('Date specified as %s but must be of the form: yyyy or yyyy-mm or yyyy-mm-dd ' % str(value))
def is_integer(value):
try:
value = int(value)
if value >= 0:
return True
except ValueError:
pass
raise ValueError('Integer specified as %s but must be a positive integer.' % str(value))
crossrefapi-1.5.0/setup.cfg 0000664 0000000 0000000 00000000214 13671736363 0015672 0 ustar 00root root 0000000 0000000 [nosetests]
match = ^test
nocapture = 1
cover-package = crossref
with-coverage = 1
cover-erase = 0
[metadata]
description-file = README.rst crossrefapi-1.5.0/setup.py 0000664 0000000 0000000 00000001503 13671736363 0015565 0 ustar 00root root 0000000 0000000 #!/usr/bin/env python
from setuptools import setup, find_packages
from crossref import VERSION
install_requires = [
'requests>=2.11.1'
]
tests_require = []
setup(
name="crossrefapi",
version=VERSION,
description="Library that implements the endpoints of the Crossref API",
author="SciELO",
author_email="scielo-dev@googlegroups.com",
maintainer="Fabio Batalha",
maintainer_email="fabiobatalha@gmail.com",
url="http://github.com/fabiobatalha/crossrefapi",
packages=find_packages(),
include_package_data=True,
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"Programming Language :: Python :: 3",
],
dependency_links=[],
tests_require=tests_require,
test_suite='tests',
install_requires=install_requires
)
crossrefapi-1.5.0/tests/ 0000775 0000000 0000000 00000000000 13671736363 0015216 5 ustar 00root root 0000000 0000000 crossrefapi-1.5.0/tests/__init__.py 0000664 0000000 0000000 00000000001 13671736363 0017316 0 ustar 00root root 0000000 0000000
crossrefapi-1.5.0/tests/test_restful.py 0000664 0000000 0000000 00000010253 13671736363 0020314 0 ustar 00root root 0000000 0000000 # coding: utf-8
import unittest
from crossref import restful
from crossref import VERSION
class RestfulTest(unittest.TestCase):
"""
These tests are testing the API live integration, the main purpouse of these
testes is to validate the JSON structure of the API results.
These tests may lead to connectivity erros if the Crossref API is temporary
out of service.
"""
def setUp(self):
self.etiquette = restful.Etiquette(
application_name='UnitTest CrossrefAPI',
application_version=VERSION,
application_url='https://github.com/fabiobatalha/crossrefapi',
contact_email='undefined'
)
def test_work_agency_message(self):
"""
Testing the base structure for the /works/{DOI}/agency endpoint.
If all the base structure is present, this test may not lead to dict
keyerror exceptions.
"""
works = restful.Works(etiquette=self.etiquette)
result = works.agency('10.1590/S0102-09352010000200002')
self.assertEqual(result['agency']['id'], 'crossref')
def test_work_agency_header(self):
"""
Testing the base structure for the /works/{DOI}/agency endpoint.
If all the base structure is present, this test may not lead to dict
keyerror exceptions.
"""
works = restful.Works(etiquette=self.etiquette)
result = works.agency('10.1590/S0102-09352010000200002', only_message=False)
self.assertEqual(result['message-type'], 'work-agency')
def test_work_select_fields(self):
result = restful.Works(etiquette=self.etiquette).select('DOI').url
self.assertEqual(result, 'https://api.crossref.org/works?select=DOI')
def test_work_select_fields_multiple_parameter_and_array(self):
result = restful.Works(etiquette=self.etiquette).select('DOI', 'title').select('subject').select(['relation', 'editor']).select('relation, editor').url
self.assertEqual(result, 'https://api.crossref.org/works?select=DOI%2Ceditor%2Crelation%2Csubject%2Ctitle')
def test_work_with_sample(self):
result = restful.Works(etiquette=self.etiquette).sample(5).url
self.assertEqual(result, 'https://api.crossref.org/works?sample=5')
def test_work_with_sample_and_filters(self):
result = restful.Works(etiquette=self.etiquette).filter(type='journal-article').sample(5).url
self.assertEqual(result, 'https://api.crossref.org/works?filter=type%3Ajournal-article&sample=5')
def test_members_filters(self):
result = restful.Members(etiquette=self.etiquette).filter(has_public_references="true").url
self.assertEqual(result, 'https://api.crossref.org/members?filter=has-public-references%3Atrue')
def test_funders_filters(self):
result = restful.Funders(etiquette=self.etiquette).filter(location="Japan").url
self.assertEqual(result, 'https://api.crossref.org/funders?filter=location%3AJapan')
class HTTPRequestTest(unittest.TestCase):
def setUp(self):
self.httprequest = restful.HTTPRequest()
def test_default_rate_limits(self):
expected = {'X-Rate-Limit-Interval': 1, 'X-Rate-Limit-Limit': 50}
self.assertEqual(self.httprequest.rate_limits, expected)
def test_update_rate_limits_seconds(self):
headers = {'X-Rate-Limit-Interval': '2s', 'X-Rate-Limit-Limit': 50}
self.httprequest._update_rate_limits(headers)
expected = {'X-Rate-Limit-Interval': 2, 'X-Rate-Limit-Limit': 50}
self.assertEqual(self.httprequest.rate_limits, expected)
def test_update_rate_limits_minutes(self):
headers = {'X-Rate-Limit-Interval': '2m', 'X-Rate-Limit-Limit': 50}
self.httprequest._update_rate_limits(headers)
expected = {'X-Rate-Limit-Interval': 120, 'X-Rate-Limit-Limit': 50}
self.assertEqual(self.httprequest.rate_limits, expected)
def test_update_rate_limits_hours(self):
headers = {'X-Rate-Limit-Interval': '2h', 'X-Rate-Limit-Limit': 50}
self.httprequest._update_rate_limits(headers)
expected = {'X-Rate-Limit-Interval': 7200, 'X-Rate-Limit-Limit': 50}
self.assertEqual(self.httprequest.rate_limits, expected)
crossrefapi-1.5.0/tests/test_validators.py 0000664 0000000 0000000 00000004454 13671736363 0021006 0 ustar 00root root 0000000 0000000 # coding: utf-8
import unittest
from crossref import validators
class ValidatorsTest(unittest.TestCase):
def test_directory_1(self):
result = validators.directory('DOAJ')
self.assertTrue(result)
def test_directory_2(self):
with self.assertRaises(ValueError):
validators.directory('any invalid archive')
def test_archive_1(self):
result = validators.archive('CLOCKSS')
self.assertTrue(result)
def test_archive_2(self):
with self.assertRaises(ValueError):
validators.archive('any invalid archive')
def test_document_type_1(self):
result = validators.document_type('book-chapter')
self.assertTrue(result)
def test_document_type_2(self):
with self.assertRaises(ValueError):
validators.document_type('any invalid type')
def test_is_bool_3(self):
result = validators.is_bool('true')
self.assertTrue(result)
def test_is_bool_4(self):
result = validators.is_bool('false')
self.assertTrue(result)
def test_is_bool_5(self):
result = validators.is_bool('1')
self.assertTrue(result)
def test_is_bool_5(self):
with self.assertRaises(ValueError):
validators.is_bool('jljlj')
def test_is_date_1(self):
result = validators.is_date('2017')
self.assertTrue(result)
def test_is_date_2(self):
result = validators.is_date('2017-12')
self.assertTrue(result)
def test_is_date_3(self):
result = validators.is_date('2017-12-31')
self.assertTrue(result)
def test_is_date_4(self):
with self.assertRaises(ValueError):
validators.is_date('asas')
def test_is_date_5(self):
with self.assertRaises(ValueError):
validators.is_date('2017-30')
def test_is_date_6(self):
with self.assertRaises(ValueError):
validators.is_date('2017-12-00')
def test_is_integer_1(self):
result = validators.is_integer('10')
self.assertTrue(result)
def test_is_integer_1(self):
with self.assertRaises(ValueError):
validators.is_integer('-1')
def test_is_integer_3(self):
with self.assertRaises(ValueError):
validators.is_integer('dd')