pax_global_header00006660000000000000000000000064147012731770014523gustar00rootroot0000000000000052 comment=bce414d32fb8ccc394fd859381d64bed544dc332 crossrefapi-1.6.1/000077500000000000000000000000001470127317700140505ustar00rootroot00000000000000crossrefapi-1.6.1/.gitignore000066400000000000000000000022271470127317700160430ustar00rootroot00000000000000# 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/ # pycharm .idea crossrefapi-1.6.1/.pre-commit-config.yaml000066400000000000000000000001531470127317700203300ustar00rootroot00000000000000repos: - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.277 hooks: - id: ruffcrossrefapi-1.6.1/.ruff.toml000066400000000000000000000027311470127317700157700ustar00rootroot00000000000000select = [ "E", # pycodestyle errors "F", # pyflakes "B", # flake8-bugbear "W", # pycodestyle warnings "N", # pep8 naming "D419", "D300", "D301", "D419", # pydocstyle "UP", # pyupgrade "YTT", # flake-2020 "S", # flake8-bandit # "BLE", # flake8-blind-except # "FBT", # flake8-boolean-trap "A", # flake8-builtins "COM", # flake8-commas "C4", # flake8-comprehensions "DTZ", # flake8-datetimez "T10", # flake8-debugger "EM", # flake8-errmsg "EXE", # flake8-executable "ISC", # flake8-implicit-str-concat "ICN", # flake8-import-conventions "G", # flake8-logging-format "INP", # flake8-no-pep420 "PIE", # flake8-pie "T20", # flake8-print "PT", # flake8-pytest-style "Q", # flake8-quotes "RET", # flake8-return "SIM", # flake8-simplify "TID", # flake8-tidy-imports # "TCH", # flake8-type-checking -> maybe # "ARG", # flake8-unused-arguments -> No, doesn't work with pytest, django, celery... "PTH", # flake8-use-pathlib # "ERA", # eradicate -> No "PGH", # pygrep-hooks "PL", # pylint "TRY", # tryceratops "RSE", # flake8-raise "RUF", # ruff-specific # "C90", # mccabe for cyclomatic complexity -> maybe "I", # isort ] ignore = [ "S101", # Use of assert detected "PLR2004", # Magic values are ok ] target-version = "py311" line-length = 100 extend-exclude = [ ".idea", "migrations", ]crossrefapi-1.6.1/.travis.yml000066400000000000000000000004211470127317700161560ustar00rootroot00000000000000dist: bionic arch: arm64 language: python matrix: include: - python: 3.8 - python: 3.9 - python: 3.11 before_install: - pip install poetry==1.5.1 install: - poetry install --no-interaction --no-ansi -vvv script: - ruff tests - ruff crossref - pytestcrossrefapi-1.6.1/CHANGES.md000066400000000000000000000002271470127317700154430ustar00rootroot00000000000000# 1.6.1 * Fix Depositor including timeout attribute to it # 1.6.0 * Major packaging re-structuration * Using poetry * No changes in the source codecrossrefapi-1.6.1/LICENSE000066400000000000000000000024461470127317700150630ustar00rootroot00000000000000BSD 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.6.1/MANIFEST.in000066400000000000000000000000471470127317700156070ustar00rootroot00000000000000recursive-include articlemeta *.thrift crossrefapi-1.6.1/README.rst000066400000000000000000000673631470127317700155560ustar00rootroot00000000000000------------------- 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 -------------- .. code-block:: shell 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 ````` See valid parameters in :code:`Works.FIELDS_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'} Filter `````` See valid parameters in :code:`Works.FILTER_VALIDATOR`. Replace :code:`.` with :code:`__` and :code:`-` with :code:`_` when using parameters. .. code-block:: python In [1] from cross.restful import Works In [2]: works = Works() In [3]: for i in works.filter(license__url='https://creativecommons.org/licenses/by', from_pub_date='2016').sample(5).select('title'): ...: print(i) ...: {'title': ['Vers une économie circulaire... de proximité ? Une spatialité à géométrie variable']} {'title': ['The stakeholders of the Olympic System']} {'title': ["Un cas de compensation écologique dans le secteur minier : la réserve forestière Dékpa (Côte d'Ivoire) au secours des forêts et des populations locales"]} {'title': ['A simple extension of FFT-based methods to strain gradient loadings - Application to the homogenization of beams and plates with linear and non-linear behaviors']} {'title': ['Gestion des déchets ménagers dans la ville de Kinshasa : Enquête sur la perception des habitants et propositions']} Select `````` See valid parameters in :code:`Works.FIELDS_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 along with the 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 number of items a query result should retrieve. This method will not iterate through and retrieve 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.6.1/crossref/000077500000000000000000000000001470127317700156765ustar00rootroot00000000000000crossrefapi-1.6.1/crossref/__init__.py000066400000000000000000000001121470127317700200010ustar00rootroot00000000000000from importlib import metadata VERSION = metadata.version("crossrefapi") crossrefapi-1.6.1/crossref/restful.py000066400000000000000000002050131470127317700177350ustar00rootroot00000000000000from __future__ import annotations import contextlib import typing from time import sleep import requests from crossref import VERSION, validators 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: 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): with contextlib.suppress(ValueError): self.rate_limits["x-rate-limit-limit"] = int(headers.get("x-rate-limit-limit", 50)) with contextlib.suppress(ValueError): 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( # noqa: PLR0913 self, method, endpoint, data=None, files=None, timeout=100, only_headers=False, custom_header=None, ): if only_headers is True: return requests.head(endpoint, timeout=2) action = requests.post if method == "post" else requests.get headers = custom_header if custom_header else {"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 f"https://{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 "{}/{} ({}; mailto:{}) BasedOn: CrossrefAPI/{}".format( self.application_name, self.application_version, self.application_url, self.contact_email, VERSION, ) class Endpoint: CURSOR_AS_ITER_METHOD = False def __init__( # noqa: PLR0913 self, request_url=None, request_params=None, context=None, etiquette=None, throttle=True, crossref_plus_token=None, timeout=30, ): 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 {} self.context = context or "" self.timeout = timeout @property def _rate_limits(self): request_url = str(self.request_url) result = self.do_http_request( "get", request_url, only_headers=True, custom_header=self.custom_header, timeout=self.timeout, throttle=False, ) return { "x-rate-limit-limit": result.headers.get("x-rate-limit-limit", "undefined"), "x-rate-limit-interval": result.headers.get("x-rate-limit-interval", "undefined"), } def _escaped_pagging(self): escape_pagging = ["offset", "rows"] request_params = dict(self.request_params) for item in escape_pagging: with contextlib.suppress(KeyError): del request_params[item] 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, timeout=self.timeout, ).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, timeout=self.timeout, ).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, request_params: dict | None): # noqa: A003 context = str(self.context) request_url = build_url_endpoint(self.ENDPOINT, context) if request_params is None: request_params = {} return iter( self.__class__( request_url=request_url, request_params=request_params, context=context, etiquette=self.etiquette, crossref_plus_token=self.crossref_plus_token, timeout=self.timeout, ), ) def __iter__(self): # noqa: PLR0912 - To many branches is not a problem. 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, timeout=self.timeout, ) if result.status_code == 404: return 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, timeout=self.timeout, ) if result.status_code == 404: return 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, timeout=self.timeout, ) if result.status_code == 404: return result = result.json() if len(result["message"]["items"]) == 0: return for item in result["message"]["items"]: yield item request_params["offset"] += LIMIT if request_params["offset"] >= MAXOFFSET: msg = "Offset exceded the max offset of %d" raise MaxOffsetError(msg, 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 = ( "DOI", "ISBN", "ISSN", "URL", "abstract", "accepted", "alternative-id", "approved", "archive", "article-number", "assertion", "author", "chair", "clinical-trial-number", "container-title", "content-created", "content-domain", "created", "degree", "deposited", "editor", "event", "funder", "group-title", "indexed", "is-referenced-by-count", "issn-type", "issue", "issued", "license", "link", "member", "original-title", "page", "posted", "prefix", "published", "published-online", "published-print", "publisher", "publisher-location", "reference", "references-count", "relation", "score", "short-container-title", "short-title", "standards-body", "subject", "subtitle", "title", "translator", "type", "update-policy", "update-to", "updated-by", "volume", ) FILTER_VALIDATOR: typing.ClassVar[dict] = { "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: typing.ClassVar[dict] = { "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 ... an-1-one'] 2007-02-13T20:56:13Z ['Contributions to the F ... 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: ... , Zika virus, what's next?"] 2017-05-29T12:55:53Z ['Sensitivity of RT-PCR method ... or competence studies'] 2017-05-29T12:53:54Z ['Re-evaluation of routine den ... a 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: msg = "Sort order specified as {} but must be one of: {}".format(str(order), ", ".join( self.ORDER_VALUES)) raise UrlSyntaxError( msg, ) request_params["order"] = order return self.__class__( request_url=request_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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'} {'DOI': '10.1016/j.mssp.2015.07.076', 'prefix': '10.1016'} {'DOI': '10.1002/slct.201700168', 'prefix': '10.1002'} {'DOI': '10.1016/j.actbio.2017.01.034', 'prefix': '10.1016'} {'DOI': '10.1016/j.optcom.2013.11.013', 'prefix': '10.1016'} ... 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'} {'DOI': '10.1016/j.jneumeth.2009.08.017', 'prefix': '10.1016'} {'DOI': '10.1016/j.tetlet.2016.05.058', 'prefix': '10.1016'} {'DOI': '10.1007/s00170-017-0689-z', 'prefix': '10.1007'} {'DOI': '10.1016/j.dsr.2016.03.004', 'prefix': '10.1016'} ... 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'} {'DOI': '10.1016/j.bios.2014.04.018', 'prefix': '10.1016} {'DOI': '10.1016/j.cej.2016.10.011', 'prefix': '10.1016'} {'DOI': '10.1016/j.dci.2017.08.001', 'prefix': '10.1016'} {'DOI': '10.1016/j.icheatmasstransfer.2016.09.012', 'prefix': '10.1016'} ... 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'} {'DOI': '10.1016/j.bios.2014.04.018', 'prefix': '10.1016'} {'DOI': '10.1016/j.cej.2016.10.011', 'prefix': '10.1016'} {'DOI': '10.1016/j.dci.2017.08.001', 'prefix': '10.1016'} {'DOI': '10.1016/j.icheatmasstransfer.2016.09.012', 'prefix': '10.1016'} ... """ 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: msg = "Select field's specified as ({}) but must be one of: {}".format( ", ".join(invalid_select_args), ", ".join(self.FIELDS_SELECT)) raise UrlSyntaxError( msg, ) 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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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 ... multiprofissionais na saúde coletiva'] ['Aprendizagem em grupo operativo de diabetes: uma abordagem etnográfica'] ['A rotatividade de enfermeiros e médicos: ... 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: ... 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: msg = "Sort field specified as {} but must be one of: {}".format(str(sort), ", ".join( self.SORT_VALUES)) raise UrlSyntaxError( msg, ) request_params["sort"] = sort return self.__class__( request_url=request_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) def filter(self, **kwargs): # noqa: A003 """ 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. Replace `.` with `__` and `-` with `_` when using parameters. 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 ... tients 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(): msg = ( f"Filter {decoded_fltr!s} specified but there is no such filter for" f" this route. Valid filters for this route" f" are: {', '.join(self.FILTER_VALIDATOR.keys())}" ) raise UrlSyntaxError( msg, ) 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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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(): msg = ( f"Facet {facet_name} specified but there is no such facet for this route." f" Valid facets for this route are: *, affiliation, funder-name, funder-doi," f" publisher-name, orcid, container-title, assertion, archive, update-type," f" issn, published, source, type-name, license, category-name, relation-type," f" assertion-group" ) raise UrlSyntaxError(( msg ), ", ".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"] = f"{facet_name}:{facet_count}" result = self.do_http_request( "get", request_url, data=request_params, custom_header=self.custom_header, timeout=self.timeout, ).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: msg = ( f"Field query {field!s} specified but there is no such field query for" " this route." f" Valid field queries for this route are: {', '.join(self.FIELDS_QUERY)}" ) raise UrlSyntaxError( msg, ) request_params["query.%s" % field.replace("_", "-")] = value return self.__class__(request_url=request_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout) 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 ... 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: msg = ( f"Integer specified as {sample_size!s} but" " must be a positive integer less than or equal to 100." ) raise UrlSyntaxError(msg) # noqa: TRY301 except TypeError as exc: msg = ( f"Integer specified as {sample_size!s} but" " must be a positive integer less than or equal to 100." ) raise UrlSyntaxError(msg) from exc request_params["sample"] = sample_size return self.__class__( request_url=request_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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 ... 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, timeout=self.timeout, ) if result.status_code == 404: return None 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-2...5000001', '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, timeout=self.timeout, ) if result.status_code == 404: return None 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, timeout=self.timeout, ) if result.status_code == 404: return False return True class Funders(Endpoint): CURSOR_AS_ITER_METHOD = False ENDPOINT = "funders" FILTER_VALIDATOR: typing.ClassVar[dict] = {"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_url, request_params=request_params, etiquette=self.etiquette, timeout=self.timeout, ) def filter(self, **kwargs): # noqa: A003 """ 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(): msg = ( f"Filter {decoded_fltr!s} specified but there is no such filter for this route." f" Valid filters for this route are: {', '.join(self.FILTER_VALIDATOR.keys())}" ) raise UrlSyntaxError( msg, ) 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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) def funder(self, funder_id, only_message=True): """ 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, timeout=self.timeout, ) if result.status_code == 404: return None 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, timeout=self.timeout, ) 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 = f"{self.ENDPOINT}/{funder_id!s}" return Works(context=context) class Members(Endpoint): CURSOR_AS_ITER_METHOD = False ENDPOINT = "members" FILTER_VALIDATOR: typing.ClassVar[dict] = { "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 ... Association'], 'breakdowns': {'dois-by-issued-year': []}, 'location': 'Dongsin Tow ... llae-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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) def filter(self, **kwargs): # noqa: A003 """ 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(): msg = ( f"Filter {decoded_fltr!s} specified but there is no such filter for this route." f" Valid filters for this route are: {', '.join(self.FILTER_VALIDATOR.keys())}" ) raise UrlSyntaxError(msg) 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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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, timeout=self.timeout, ) if result.status_code == 404: return None 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, timeout=self.timeout, ) 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 = f"{self.ENDPOINT}/{member_id!s}" return Works(context=context) class Types(Endpoint): CURSOR_AS_ITER_METHOD = False ENDPOINT = "types" def type(self, type_id, only_message=True): # noqa: A003 """ 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, timeout=self.timeout, ) if result.status_code == 404: return None result = result.json() return result["message"] if only_message is True else result def all(self): # noqa: A003 """ 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, timeout=self.timeout, ) if result.status_code == 404: return result = result.json() yield from result["message"]["items"] 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, timeout=self.timeout, ) 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 = f"{self.ENDPOINT}/{type_id!s}" 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, timeout=self.timeout, ) if result.status_code == 404: return None 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 = f"{self.ENDPOINT}/{prefix_id!s}" 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_url, request_params=request_params, context=context, etiquette=self.etiquette, timeout=self.timeout, ) 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, timeout=self.timeout, ) if result.status_code == 404: return None 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, timeout=self.timeout, ) 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 = f"{self.ENDPOINT}/{issn!s}" return Works(context=context) class Depositor: def __init__( # noqa: PLR0913 self, prefix, api_user, api_key, etiquette=None, use_test_server=False, timeout=100, ): 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 self.timeout = timeout def get_endpoint(self, verb): subdomain = "test" if self.use_test_server else "doi" return f"https://{subdomain}.crossref.org/servlet/{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, } return self.do_http_request( "post", endpoint, data=params, files=files, custom_header=self.custom_header, timeout=self.timeout, ) 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, } return self.do_http_request( "get", endpoint, data=params, custom_header=self.custom_header, timeout=self.timeout, ) 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, } return self.do_http_request( "get", endpoint, data=params, custom_header=self.custom_header, timeout=self.timeout, ) crossrefapi-1.6.1/crossref/utils.py000066400000000000000000000006451470127317700174150ustar00rootroot00000000000000 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.6.1/crossref/validators.py000066400000000000000000000044251470127317700204250ustar00rootroot00000000000000from datetime import datetime def directory(value): expected = "DOAJ" if str(value) in expected: return True msg = "Directory specified as {} but must be one of: {}".format(str(value), ", ".join(expected)) raise ValueError( msg, ) def archive(value): expected = ("Portico", "CLOCKSS", "DWT") if str(value) in expected: return True msg = "Archive specified as {} but must be one of: {}".format(str(value), ", ".join(expected)) raise ValueError( msg, ) 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 msg = "Type specified as {} but must be one of: {}".format(str(value), ", ".join(expected)) raise ValueError( msg, ) def is_bool(value): expected = ["t", "true", "1", "f", "false", "0"] if str(value) in expected: return True msg = "Boolean specified {} True but must be one of: {}".format(str(value), ", ".join(expected)) raise ValueError( msg, ) def is_date(value): try: datetime.strptime(value, "%Y") # noqa: DTZ007 except ValueError: try: datetime.strptime(value, "%Y-%m") # noqa: DTZ007 except ValueError: try: datetime.strptime(value, "%Y-%m-%d") # noqa: DTZ007 except ValueError as exc: msg = f"Invalid date {value}." raise ValueError(msg) from exc return True 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.6.1/poetry.lock000066400000000000000000001341741470127317700162560ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. [[package]] name = "asttokens" version = "2.4.1" description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] [package.dependencies] six = ">=1.12.0" [package.extras] astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] [[package]] name = "certifi" version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." optional = false python-versions = ">=3.8" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "charset-normalizer" version = "3.3.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, ] [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "decorator" version = "5.1.1" description = "Decorators for Humans" optional = false python-versions = ">=3.5" files = [ {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, ] [[package]] name = "distlib" version = "0.3.8" description = "Distribution utilities" optional = false python-versions = "*" files = [ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, ] [[package]] name = "exceptiongroup" version = "1.2.2" description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "executing" version = "2.1.0" description = "Get the currently executing AST node of a frame, and other information" optional = false python-versions = ">=3.8" files = [ {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, ] [package.extras] tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] [[package]] name = "filelock" version = "3.16.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ {file = "filelock-3.16.1-py3-none-any.whl", hash = "sha256:2082e5703d51fbf98ea75855d9d5527e33d8ff23099bec374a134febee6946b0"}, {file = "filelock-3.16.1.tar.gz", hash = "sha256:c249fbfcd5db47e5e2d6d62198e565475ee65e4831e2561c8e313fa7eb961435"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] [[package]] name = "identify" version = "2.6.1" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ {file = "identify-2.6.1-py2.py3-none-any.whl", hash = "sha256:53863bcac7caf8d2ed85bd20312ea5dcfc22226800f6d6881f232d861db5a8f0"}, {file = "identify-2.6.1.tar.gz", hash = "sha256:91478c5fb7c3aac5ff7bf9b4344f803843dc586832d5f110d672b19aa1984c98"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "idna" version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "ipython" version = "8.28.0" description = "IPython: Productive Interactive Computing" optional = false python-versions = ">=3.10" files = [ {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} jedi = ">=0.16" matplotlib-inline = "*" pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} prompt-toolkit = ">=3.0.41,<3.1.0" pygments = ">=2.4.0" stack-data = "*" traitlets = ">=5.13.0" typing-extensions = {version = ">=4.6", markers = "python_version < \"3.12\""} [package.extras] all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] black = ["black"] doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] kernel = ["ipykernel"] matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "jedi" version = "0.19.1" description = "An autocompletion tool for Python that can be used for text editors." optional = false python-versions = ">=3.6" files = [ {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, ] [package.dependencies] parso = ">=0.8.3,<0.9.0" [package.extras] docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] [[package]] name = "matplotlib-inline" version = "0.1.7" description = "Inline Matplotlib backend for Jupyter" optional = false python-versions = ">=3.8" files = [ {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, ] [package.dependencies] traitlets = "*" [[package]] name = "nodeenv" version = "1.9.1" description = "Node.js virtual environment builder" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, ] [[package]] name = "packaging" version = "24.1" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" files = [ {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] name = "parso" version = "0.8.4" description = "A Python Parser" optional = false python-versions = ">=3.6" files = [ {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, ] [package.extras] qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] testing = ["docopt", "pytest"] [[package]] name = "pexpect" version = "4.9.0" description = "Pexpect allows easy control of interactive console applications." optional = false python-versions = "*" files = [ {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, ] [package.dependencies] ptyprocess = ">=0.5" [[package]] name = "platformdirs" version = "4.3.6" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, ] [package.extras] docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] type = ["mypy (>=1.11.2)"] [[package]] name = "pluggy" version = "1.5.0" description = "plugin and hook calling mechanisms for python" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "3.8.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ {file = "pre_commit-3.8.0-py2.py3-none-any.whl", hash = "sha256:9a90a53bf82fdd8778d58085faf8d83df56e40dfe18f45b19446e26bf1b3a63f"}, {file = "pre_commit-3.8.0.tar.gz", hash = "sha256:8bb6494d4a20423842e198980c9ecf9f96607a07ea29549e180eef9ae80fe7af"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "prompt-toolkit" version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] wcwidth = "*" [[package]] name = "ptyprocess" version = "0.7.0" description = "Run a subprocess in a pseudo terminal" optional = false python-versions = "*" files = [ {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] [[package]] name = "pure-eval" version = "0.2.3" description = "Safely evaluate AST nodes without side effects" optional = false python-versions = "*" files = [ {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, ] [package.extras] tests = ["pytest"] [[package]] name = "pygments" version = "2.18.0" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" files = [ {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, ] [package.extras] windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pytest" version = "7.4.4" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pyyaml" version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, ] [[package]] name = "requests" version = "2.32.3" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" files = [ {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, ] [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] [[package]] name = "ruff" version = "0.0.277" description = "An extremely fast Python linter, written in Rust." optional = false python-versions = ">=3.7" files = [ {file = "ruff-0.0.277-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:3250b24333ef419b7a232080d9724ccc4d2da1dbbe4ce85c4caa2290d83200f8"}, {file = "ruff-0.0.277-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:3e60605e07482183ba1c1b7237eca827bd6cbd3535fe8a4ede28cbe2a323cb97"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7baa97c3d7186e5ed4d5d4f6834d759a27e56cf7d5874b98c507335f0ad5aadb"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:74e4b206cb24f2e98a615f87dbe0bde18105217cbcc8eb785bb05a644855ba50"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:479864a3ccd8a6a20a37a6e7577bdc2406868ee80b1e65605478ad3b8eb2ba0b"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:468bfb0a7567443cec3d03cf408d6f562b52f30c3c29df19927f1e0e13a40cd7"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f32ec416c24542ca2f9cc8c8b65b84560530d338aaf247a4a78e74b99cd476b4"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:14a7b2f00f149c5a295f188a643ac25226ff8a4d08f7a62b1d4b0a1dc9f9b85c"}, {file = "ruff-0.0.277-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9879f59f763cc5628aa01c31ad256a0f4dc61a29355c7315b83c2a5aac932b5"}, {file = "ruff-0.0.277-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f612e0a14b3d145d90eb6ead990064e22f6f27281d847237560b4e10bf2251f3"}, {file = "ruff-0.0.277-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:323b674c98078be9aaded5b8b51c0d9c424486566fb6ec18439b496ce79e5998"}, {file = "ruff-0.0.277-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3a43fbe026ca1a2a8c45aa0d600a0116bec4dfa6f8bf0c3b871ecda51ef2b5dd"}, {file = "ruff-0.0.277-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:734165ea8feb81b0d53e3bf523adc2413fdb76f1264cde99555161dd5a725522"}, {file = "ruff-0.0.277-py3-none-win32.whl", hash = "sha256:88d0f2afb2e0c26ac1120e7061ddda2a566196ec4007bd66d558f13b374b9efc"}, {file = "ruff-0.0.277-py3-none-win_amd64.whl", hash = "sha256:6fe81732f788894a00f6ade1fe69e996cc9e485b7c35b0f53fb00284397284b2"}, {file = "ruff-0.0.277-py3-none-win_arm64.whl", hash = "sha256:2d4444c60f2e705c14cd802b55cd2b561d25bf4311702c463a002392d3116b22"}, {file = "ruff-0.0.277.tar.gz", hash = "sha256:2dab13cdedbf3af6d4427c07f47143746b6b95d9e4a254ac369a0edb9280a0d2"}, ] [[package]] name = "six" version = "1.16.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] [[package]] name = "stack-data" version = "0.6.3" description = "Extract data from python stack frames and tracebacks for informative displays" optional = false python-versions = "*" files = [ {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, ] [package.dependencies] asttokens = ">=2.1.0" executing = ">=1.2.0" pure-eval = "*" [package.extras] tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] [[package]] name = "tomli" version = "2.0.2" description = "A lil' TOML parser" optional = false python-versions = ">=3.8" files = [ {file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"}, {file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"}, ] [[package]] name = "traitlets" version = "5.14.3" description = "Traitlets Python configuration system" optional = false python-versions = ">=3.8" files = [ {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, ] [package.extras] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] [[package]] name = "typing-extensions" version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] name = "urllib3" version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" version = "20.26.6" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.26.6-py3-none-any.whl", hash = "sha256:7345cc5b25405607a624d8418154577459c3e0277f5466dd79c49d5e492995f2"}, {file = "virtualenv-20.26.6.tar.gz", hash = "sha256:280aede09a2a5c317e409a00102e7077c6432c5a38f0ef938e643805a7ad2c48"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<5" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "wcwidth" version = "0.2.13" description = "Measures the displayed width of unicode strings in a terminal" optional = false python-versions = "*" files = [ {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, ] [metadata] lock-version = "2.0" python-versions = "^3.10" content-hash = "0da5aa4acc06d8b5853e375f1313784cb22519528806b62b296086efae886b48" crossrefapi-1.6.1/pyproject.toml000066400000000000000000000010341470127317700167620ustar00rootroot00000000000000[tool.poetry] name = "crossrefapi" version = "1.6.1" description = "Library that implements the endpoints of the Crossref API" authors = ["Fabio Batalha "] packages = [ { include = "crossref", from="."} ] [tool.poetry.dependencies] python = "^3.10" requests = "^2.32.3" urllib3 = "^2.2.3" ipython = "^8.28.0" [tool.poetry.group.dev.dependencies] pytest = "^7.4.0" ruff = "^0.0.277" pre-commit = "^3.3.3" ipython = "^8.20.0" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" crossrefapi-1.6.1/tests/000077500000000000000000000000001470127317700152125ustar00rootroot00000000000000crossrefapi-1.6.1/tests/__init__.py000066400000000000000000000000011470127317700173120ustar00rootroot00000000000000 crossrefapi-1.6.1/tests/test_restful.py000066400000000000000000000101241470127317700203050ustar00rootroot00000000000000 import unittest from crossref import VERSION, restful 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") assert 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) assert result["message-type"] == "work-agency" def test_work_select_fields(self): result = restful.Works(etiquette=self.etiquette).select("DOI").url assert 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 assert 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 assert 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 assert 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 assert 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 assert 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} assert 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} assert 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} assert 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} assert self.httprequest.rate_limits == expected crossrefapi-1.6.1/tests/test_validators.py000066400000000000000000000042701470127317700207760ustar00rootroot00000000000000 import unittest from crossref import validators class ValidatorsTest(unittest.TestCase): def test_directory_1(self): result = validators.directory("DOAJ") assert result def test_directory_2(self): with self.assertRaises(ValueError): validators.directory("any invalid archive") def test_archive_1(self): result = validators.archive("CLOCKSS") assert 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") assert 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") assert result def test_is_bool_4(self): result = validators.is_bool("false") assert result def test_is_bool_5(self): result = validators.is_bool("1") assert 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") assert result def test_is_date_2(self): result = validators.is_date("2017-12") assert result def test_is_date_3(self): result = validators.is_date("2017-12-31") assert 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") assert 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")