pax_global_header00006660000000000000000000000064136717363630014531gustar00rootroot0000000000000052 comment=090b62c04c74d41284f1b3320bf32fa55008d4d0 crossrefapi-1.5.0/000077500000000000000000000000001367173636300140545ustar00rootroot00000000000000crossrefapi-1.5.0/.gitignore000066400000000000000000000022051367173636300160430ustar00rootroot00000000000000# 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.yml000066400000000000000000000002451367173636300161660ustar00rootroot00000000000000language: python python: - "3.5" - "2.7" before_install: - pip install --upgrade setuptools pip install: python setup.py develop script: python setup.py testcrossrefapi-1.5.0/LICENSE000066400000000000000000000024461367173636300150670ustar00rootroot00000000000000BSD 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.in000066400000000000000000000000471367173636300156130ustar00rootroot00000000000000recursive-include articlemeta *.thrift crossrefapi-1.5.0/README.rst000066400000000000000000000651051367173636300155520ustar00rootroot00000000000000------------------- 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\nSUCCESS\n\n\n

SUCCESS

\n

Your 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/000077500000000000000000000000001367173636300157025ustar00rootroot00000000000000crossrefapi-1.5.0/crossref/__init__.py000066400000000000000000000000221367173636300200050ustar00rootroot00000000000000VERSION = '1.4.0' crossrefapi-1.5.0/crossref/restful.py000066400000000000000000002011341367173636300177410ustar00rootroot00000000000000# 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.py000066400000000000000000000006661367173636300174240ustar00rootroot00000000000000# 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.py000066400000000000000000000046601367173636300204320ustar00rootroot00000000000000# 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.cfg000066400000000000000000000002141367173636300156720ustar00rootroot00000000000000[nosetests] match = ^test nocapture = 1 cover-package = crossref with-coverage = 1 cover-erase = 0 [metadata] description-file = README.rstcrossrefapi-1.5.0/setup.py000066400000000000000000000015031367173636300155650ustar00rootroot00000000000000#!/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/000077500000000000000000000000001367173636300152165ustar00rootroot00000000000000crossrefapi-1.5.0/tests/__init__.py000066400000000000000000000000011367173636300173160ustar00rootroot00000000000000 crossrefapi-1.5.0/tests/test_restful.py000066400000000000000000000102531367173636300203140ustar00rootroot00000000000000# 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.py000066400000000000000000000044541367173636300210060ustar00rootroot00000000000000# 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')