├── test ├── __init__.py ├── test-occurrences-download_get.py ├── test-occurrences-download_sql.py ├── test-occurrences-download_citation.py ├── test-occurrences-download_cancel.py ├── test-species-name_suggest.py ├── test-occurrences-download_describe.py ├── test-wkt_rewind.py ├── test-registry-dataset_metrics.py ├── test-occurrences-get.py ├── test-registry-datasets.py ├── test-registry-networks.py ├── test-registry-nodes.py ├── test-species-name_usage.py ├── test-registry-installations.py ├── test-registry-organizations.py ├── vcr_cassettes │ ├── test_download_cancel.yaml │ ├── test_count.yaml │ ├── test-occurrences-download_citation.yaml │ ├── test_name_backbone_class.yaml │ ├── test_count_year.yaml │ ├── test_nodes_return.yaml │ ├── test_count_basisofrecord.yaml │ ├── test_name_lookup_faceting.yaml │ ├── test_name_usage.yaml │ ├── test_name_backbone.yaml │ ├── test_networks_uuid.yaml │ ├── test_count_publishingcountries.yaml │ ├── test_dataset_metrics.yaml │ ├── test_name_backbone_checklistKey.yaml │ ├── test_installations_uuid.yaml │ ├── test_get_verbatim.yaml │ ├── test_name_suggest_paging.yaml │ ├── test_get_fragment.yaml │ ├── test_literature_search_key.yaml │ ├── test_organizations_uuid.yaml │ ├── test_dataset_metrics_multiple_uuids.yaml │ ├── test_count_countries.yaml │ ├── test_name_lookup_paging.yaml │ ├── test_get.yaml │ ├── test_download_describe.yaml │ └── test_count_schema.yaml ├── test-species-name_lookup.py ├── test-occurrences-search.py ├── test-species-name-backbone.py ├── test-literature-search.py ├── test-institution-search.py ├── test-collection-search.py ├── test-maps-map.py └── test-occurrences-count.py ├── MANIFEST.in ├── docs ├── changelog_link.rst ├── docs │ ├── faq.rst │ └── usecases.rst ├── intro │ └── install.rst ├── requirements.txt ├── modules │ ├── caching.rst │ ├── maps.rst │ ├── utils.rst │ ├── literature.rst │ ├── intro.rst │ ├── institution.rst │ ├── collection.rst │ ├── species.rst │ ├── registry.rst │ └── occurrence.rst ├── contributors.rst ├── license.rst ├── conduct.rst ├── contributing.rst └── index.rst ├── pygbif ├── maps │ └── __init__.py ├── utils │ ├── __init__.py │ └── wkt_rewind.py ├── package_metadata.py ├── collection │ ├── __init__.py │ └── search.py ├── institution │ └── __init__.py ├── literature │ └── __init__.py ├── registry │ ├── __init__.py │ ├── networks.py │ ├── organizations.py │ ├── installations.py │ └── nodes.py ├── species │ ├── name_parser.py │ ├── __init__.py │ ├── name_suggest.py │ └── name_usage.py ├── occurrences │ ├── get.py │ ├── __init__.py │ └── count.py ├── __init__.py ├── caching.py └── gbifutils.py ├── setup.cfg ├── gbif_map.png ├── pytest.ini ├── pyproject.toml ├── .github ├── issue_template.md ├── dependabot.yml ├── CONTRIBUTING.md ├── pull_request_template.md └── workflows │ ├── python.yml │ └── python_live_tests.yml ├── tox.ini ├── requirements.txt ├── .gitignore ├── Makefile ├── .readthedocs.yaml ├── LICENSE ├── setup.py ├── RELEASING.md ├── CONDUCT.md └── README.rst /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include *.rst *.md LICENSE 2 | -------------------------------------------------------------------------------- /docs/changelog_link.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../Changelog.rst 2 | -------------------------------------------------------------------------------- /pygbif/maps/__init__.py: -------------------------------------------------------------------------------- 1 | from .map import map, GbifMap 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description_file = README.rst 3 | -------------------------------------------------------------------------------- /pygbif/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .wkt_rewind import wkt_rewind 2 | -------------------------------------------------------------------------------- /gbif_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbif/pygbif/HEAD/gbif_map.png -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = test-*.py 3 | testpaths = test 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["setuptools"] 3 | build-backend = "setuptools.build_meta" 4 | -------------------------------------------------------------------------------- /pygbif/package_metadata.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.6.6" 2 | __title__ = "pygbif" 3 | __author__ = "Scott Chamberlain" 4 | __license__ = "MIT" 5 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /pygbif/collection/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF collection APIs methods 3 | 4 | * `search`: Search for collections in GRSciColl 5 | 6 | """ 7 | 8 | from .search import search 9 | -------------------------------------------------------------------------------- /pygbif/institution/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF institution APIs methods 3 | 4 | * `search`: Search for institutions in GRSciColl 5 | 6 | """ 7 | 8 | from .search import search 9 | -------------------------------------------------------------------------------- /pygbif/literature/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF literature APIs methods 3 | 4 | * `search`: Search for literature indexed by GBIF 5 | 6 | """ 7 | 8 | from .search import search 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: pip 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "13:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = 3 | py34,py27 4 | 5 | [testenv] 6 | commands = 7 | python setup.py develop 8 | nosetests --with-coverage --cover-package=pygbif 9 | deps = 10 | nose 11 | coverage 12 | 13 | [testenv:py34] 14 | basepython = python3.4 15 | 16 | [testenv:py27] 17 | basepython = python2.7 18 | -------------------------------------------------------------------------------- /docs/docs/faq.rst: -------------------------------------------------------------------------------- 1 | .. _faq: 2 | 3 | Frequently Asked Questions 4 | ========================== 5 | 6 | What other GBIF clients are out there? 7 | -------------------------------------- 8 | 9 | - R: `rgbif`_ 10 | - Ruby: `gbifrb`_ 11 | 12 | .. _rgbif: https://github.com/ropensci/rgbif 13 | .. _gbifrb: https://github.com/sckott/gbifrb 14 | -------------------------------------------------------------------------------- /docs/intro/install.rst: -------------------------------------------------------------------------------- 1 | .. _intro-install: 2 | 3 | ================== 4 | Installation guide 5 | ================== 6 | 7 | Installing pygbif 8 | ================= 9 | 10 | Stable from pypi 11 | 12 | .. code-block:: console 13 | 14 | pip install pygbif 15 | 16 | Development version 17 | 18 | .. code-block:: console 19 | 20 | [sudo] pip install git+git://github.com/gbif/pygbif.git#egg=pygbif 21 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | requests>2.7 2 | geojson_rewind 3 | geomet 4 | appdirs>=1.4.3 5 | matplotlib 6 | sphinx_issues 7 | requests-cache 8 | shapely>=1.5.13 9 | # The commit introducing the following line and the version for vcrpy can be reverted 10 | # once https://github.com/kevin1024/vcrpy/issues/688 is fixed 11 | urllib3==1.26.14 12 | vcrpy==4.2.1 13 | pytest 14 | pytest-cov 15 | codecov 16 | -------------------------------------------------------------------------------- /test/test-occurrences-download_get.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - download_get methods""" 2 | from pygbif import occurrences as occ 3 | 4 | 5 | def test_download_get(): 6 | "occurrences.download_get - basic test" 7 | key = "0089857-160910150852091" 8 | res = occ.download_get(key) 9 | assert "dict" == res.__class__.__name__ 10 | assert len(res) == 3 11 | assert key == res["key"] 12 | -------------------------------------------------------------------------------- /docs/modules/caching.rst: -------------------------------------------------------------------------------- 1 | .. _caching-modules: 2 | 3 | ============== 4 | caching module 5 | ============== 6 | 7 | caching module API: 8 | 9 | * `pygbif.caching` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | import pygbif 16 | pygbif.caching(True) 17 | 18 | 19 | caching API 20 | =========== 21 | 22 | .. py:module:: pygbif 23 | :noindex: 24 | 25 | .. autofunction:: caching 26 | -------------------------------------------------------------------------------- /docs/modules/maps.rst: -------------------------------------------------------------------------------- 1 | .. _maps-modules: 2 | 3 | =========== 4 | maps module 5 | =========== 6 | 7 | maps module API: 8 | 9 | * `map` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | from pygbif import maps 16 | maps.map(taxonKey = 2435098) 17 | 18 | 19 | maps API 20 | ======== 21 | 22 | .. py:module:: pygbif 23 | :noindex: 24 | 25 | .. automethod:: maps.map 26 | 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>2.7 2 | geojson_rewind 3 | geomet 4 | appdirs>=1.4.3 5 | matplotlib 6 | sphinx_issues 7 | requests-cache>=1.0.0 8 | shapely>=1.5.13 9 | # The commit introducing the following line and the version for vcrpy can be reverted 10 | # once https://github.com/kevin1024/vcrpy/issues/688 is fixed 11 | urllib3==1.26.14 12 | vcrpy==4.2.1 13 | pytest 14 | pytest-cov 15 | codecov 16 | sphinx_rtd_theme 17 | sphinx==7.2.6 -------------------------------------------------------------------------------- /test/test-occurrences-download_sql.py: -------------------------------------------------------------------------------- 1 | from pygbif import occurrences 2 | import vcr 3 | 4 | @vcr.use_cassette('test/vcr_cassettes/test-occurrences-download_sql.yaml') 5 | def test_download_sql(): 6 | """basic test of the download_sql function""" 7 | out = occurrences.download_sql("SELECT gbifid,publishingCountry FROM occurrence WHERE publishingCountry='BE'") 8 | assert "str" == out.__class__.__name__ 9 | assert 23 == len(out) 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/modules/utils.rst: -------------------------------------------------------------------------------- 1 | .. _utils-modules: 2 | 3 | ============ 4 | utils module 5 | ============ 6 | 7 | utils module API: 8 | 9 | * `wkt_rewind` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | from pygbif import utils 16 | x = 'POLYGON((144.6 13.2, 144.6 13.6, 144.9 13.6, 144.9 13.2, 144.6 13.2))' 17 | utils.wkt_rewind(x) 18 | 19 | 20 | utils API 21 | ========= 22 | 23 | .. py:module:: pygbif 24 | :noindex: 25 | 26 | .. automethod:: utils.wkt_rewind 27 | -------------------------------------------------------------------------------- /docs/modules/literature.rst: -------------------------------------------------------------------------------- 1 | .. _literature-modules: 2 | 3 | ================= 4 | literature module 5 | ================= 6 | 7 | literature module API: 8 | 9 | * `search` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | from pygbif import literature as lit 16 | lit.search(q='climate change', limit=5) 17 | 18 | literature API 19 | =============== 20 | 21 | .. py:module:: pygbif 22 | :noindex: 23 | 24 | .. automethod:: literature.search 25 | -------------------------------------------------------------------------------- /docs/contributors.rst: -------------------------------------------------------------------------------- 1 | .. _contributors: 2 | 3 | Contributors 4 | ============ 5 | 6 | * `Scott Chamberlain `_ 7 | * `Robert Forkel `_ 8 | * `Jan Legind `_ 9 | * `Stijn Van Hoey `_ 10 | * `Peter Desmet `_ 11 | * `Nicolas Noé `_ 12 | * `Cecilie Svenningsen `_ 13 | * `John Waller `_ -------------------------------------------------------------------------------- /docs/modules/intro.rst: -------------------------------------------------------------------------------- 1 | .. _intro-modules: 2 | 3 | ============== 4 | pygbif modules 5 | ============== 6 | 7 | `pygbif` is split up into modules for each of the major groups of API methods. 8 | 9 | * Registry - Datasets, Nodes, Installations, Networks, Organizations 10 | * Species - Taxonomic names 11 | * Occurrences - Occurrence data, including the download API 12 | * Maps - Make maps 13 | 14 | You can import the entire library, or each module individually as needed. 15 | 16 | In addition, the caching method allows you to manage whether HTTP requests 17 | are cached or not. 18 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # CONTRIBUTING # 2 | 3 | ## Bugs or feature requests 4 | 5 | * Submit an issue on the [Issues page](https://github.com/gbif/pygbif/issues) - be sure to include your Python version and pygbif version 6 | 7 | ## Code formatting 8 | 9 | This project follows black. `pip install black` 10 | 11 | Run `black pygbif` before submitting a pull request to fix any formatting. `black --check pygbif` will tell you if there's suggested changes without performing those changes. 12 | 13 | ## Code of conduct 14 | 15 | Contributors need to follow the [code of conduct](https://github.com/gbif/pygbif/blob/master/CONDUCT.md) 16 | -------------------------------------------------------------------------------- /test/test-occurrences-download_citation.py: -------------------------------------------------------------------------------- 1 | from pygbif import occurrences as occ 2 | import vcr 3 | 4 | @vcr.use_cassette('test/vcr_cassettes/test-occurrences-download_citation.yaml') 5 | def test_download_citation(): 6 | res=occ.download_citation("0235283-220831081235567") 7 | assert "str" == res.__class__.__name__ 8 | "GBIF.org (2 January 2023) GBIF Occurrence Download https://doi.org/10.15468/dl.29wbtx" == res 9 | 10 | def test_download_citation_failswell(): 11 | try: 12 | occ.download_citation("dog") 13 | except ValueError as e: 14 | assert str(e) == "key must be a GBIF download key" 15 | 16 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Related Issue 7 | 10 | 11 | ## Example 12 | 14 | 15 | 17 | -------------------------------------------------------------------------------- /docs/modules/institution.rst: -------------------------------------------------------------------------------- 1 | .. _institution-modules: 2 | 3 | ========================= 4 | institution module 5 | ========================= 6 | 7 | institution module API: 8 | 9 | * `search` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | from pygbif import institution as inst 16 | inst.search(q="Kansas") 17 | inst.search(numberSpecimens = "1000,*") 18 | inst.search(source = "IH_IRN") 19 | inst.search(country = ["US","GB"]) 20 | inst.search(typeSpecimenCount = "10,100") 21 | 22 | institution API 23 | =============== 24 | .. py:module:: pygbif 25 | :noindex: 26 | 27 | .. automethod:: institution.search 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled python modules. 2 | *.pyc 3 | 4 | # Setuptools distribution folder. 5 | /dist/ 6 | 7 | # Python egg metadata, regenerated from source files by setuptools. 8 | /*.egg-info 9 | /*.egg 10 | 11 | # Dexy processing files 12 | .dexy 13 | 14 | # Build output 15 | /build/ 16 | 17 | # docs build output 18 | docs/_build/ 19 | 20 | .tox 21 | .coverage 22 | 23 | # test DwcA 24 | *.zip 25 | 26 | # vscode project info 27 | .vscode/ 28 | .history/ 29 | 30 | # pycharm project info 31 | .idea/ 32 | 33 | # ipython notebook files 34 | .ipynb_checkpoints 35 | */.ipynb_checkpoints/* 36 | 37 | # scratch files 38 | scratch.py 39 | 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build install 2 | 3 | .PHONY: build install test docs distclean dist upload 4 | 5 | build: 6 | python3 setup.py build 7 | 8 | install: build 9 | python3 setup.py install 10 | 11 | test: 12 | pytest --cov-report term --cov=pygbif test/ 13 | 14 | docs: 15 | cd docs;\ 16 | make html 17 | # open _build/html/index.html 18 | 19 | check: 20 | python3 -m twine check dist/* 21 | 22 | distclean: 23 | rm dist/* 24 | 25 | dist: 26 | python3 setup.py sdist bdist_wheel --universal 27 | 28 | register: 29 | python3 setup.py register 30 | 31 | up: 32 | python3 -m twine upload dist/* 33 | 34 | uptest: 35 | python3 -m twine upload --repository-url https://test.pypi.org/legacy/ dist/* 36 | -------------------------------------------------------------------------------- /docs/modules/collection.rst: -------------------------------------------------------------------------------- 1 | .. _collection-modules: 2 | 3 | ========================= 4 | collection module 5 | ========================= 6 | 7 | collection module API: 8 | 9 | * `search` 10 | 11 | Example usage: 12 | 13 | .. code-block:: python 14 | 15 | from pygbif import collection as coll 16 | coll.search(query="insect") 17 | coll.search(name="Insects;Entomology", limit=2) 18 | coll.search(numberSpecimens = "0,100", limit=1) 19 | coll.search(institutionKey = "6a6ac6c5-1b8a-48db-91a2-f8661274ff80") 20 | coll.search(query = "insect", country = ["US","GB"]) 21 | 22 | collection API 23 | =============== 24 | 25 | .. py:module:: pygbif 26 | :noindex: 27 | 28 | .. automethod:: collection.search 29 | -------------------------------------------------------------------------------- /docs/modules/species.rst: -------------------------------------------------------------------------------- 1 | .. _species-modules: 2 | 3 | ============== 4 | species module 5 | ============== 6 | 7 | species module API: 8 | 9 | * `name_backbone` 10 | * `name_suggest` 11 | * `name_usage` 12 | * `name_lookup` 13 | * `name_parser` 14 | 15 | Example usage: 16 | 17 | .. code-block:: python 18 | 19 | from pygbif import species 20 | species.name_suggest(q='Puma concolor') 21 | 22 | 23 | species API 24 | =========== 25 | 26 | .. py:module:: pygbif 27 | :noindex: 28 | 29 | .. automethod:: species.name_backbone 30 | .. automethod:: species.name_suggest 31 | .. automethod:: species.name_lookup 32 | .. automethod:: species.name_usage 33 | .. automethod:: species.name_parser 34 | -------------------------------------------------------------------------------- /pygbif/registry/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF registry APIs methods 3 | 4 | * `organizations`: Organizations metadata 5 | * `nodes`: Nodes metadata 6 | * `networks`: Networks metadata 7 | * `installations`: Installations metadata 8 | * `datasets`: Search for datasets and dataset metadata 9 | * `dataset_metrics`: Get details/metrics on a GBIF dataset 10 | * `dataset_suggest`: Search that returns up to 20 matching datasets 11 | * `dataset_search`: Full text search across all datasets 12 | """ 13 | 14 | from .nodes import nodes 15 | from .networks import networks 16 | from .installations import installations 17 | from .datasets import datasets, dataset_metrics, dataset_suggest, dataset_search 18 | from .organizations import organizations 19 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yaml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Set the version of Python and other tools you might need 9 | build: 10 | os: ubuntu-22.04 11 | tools: 12 | python: "3.11" 13 | 14 | # Build documentation in the docs/ directory with Sphinx 15 | sphinx: 16 | configuration: docs/conf.py 17 | fail_on_warning: true 18 | 19 | formats: 20 | - pdf 21 | # - epub 22 | 23 | # We recommend specifying your dependencies to enable reproducible builds: 24 | # https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 25 | python: 26 | install: 27 | - requirements: requirements.txt -------------------------------------------------------------------------------- /test/test-occurrences-download_cancel.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - download_cancle methods""" 2 | from pygbif import occurrences as occ 3 | import vcr 4 | import os 5 | import pytest 6 | 7 | IN_GITHUB_ACTIONS = os.getenv("GITHUB_ACTIONS") == "true" 8 | print(IN_GITHUB_ACTIONS) 9 | 10 | @pytest.mark.skipif(IN_GITHUB_ACTIONS, reason="Test doesn't work well in Github Actions.") 11 | @vcr.use_cassette("test/vcr_cassettes/test_download_cancel.yaml", filter_headers=["authorization"]) 12 | def test_download_cancel(): 13 | "occurrences.download_cancel - basic test" 14 | name_key = "789" # Odonata 15 | res = occ.download("taxonKey = " + name_key) 16 | download_key = res[0] 17 | out = occ.download_cancel(download_key) 18 | assert True == out 19 | -------------------------------------------------------------------------------- /test/test-species-name_suggest.py: -------------------------------------------------------------------------------- 1 | """Tests for species module - name_suggest methods""" 2 | import vcr 3 | import re 4 | from pygbif import species 5 | 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_name_suggest.yaml") 8 | def test_name_suggest(): 9 | "species.name_suggest - basic test" 10 | res = species.name_suggest(q="Puma concolor") 11 | assert list == res.__class__ 12 | assert True == all( 13 | [bool(re.search("Puma concolor", z["canonicalName"])) for z in res] 14 | ) 15 | 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_name_suggest_paging.yaml") 18 | def test_name_suggest_paging(): 19 | "species.name_suggest - paging" 20 | res = species.name_suggest(q="Aso", limit=3) 21 | assert list == res.__class__ 22 | assert 3 == len(res) 23 | -------------------------------------------------------------------------------- /test/test-occurrences-download_describe.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - download_describe""" 2 | from pygbif import occurrences as occ 3 | import vcr 4 | 5 | @vcr.use_cassette("test/vcr_cassettes/test_download_describe.yaml") 6 | def test_download_describe(): 7 | "occurrences.download_describe - basic usage" 8 | res=occ.download_describe("simpleCsv") 9 | assert dict == res.__class__ 10 | assert len(res["fields"]) >= 50 # unlikely to get smaller 11 | assert "gbifID" == res["fields"][0]["name"] 12 | 13 | def test_download_describe_fails_well(): 14 | "occurrences.download_describe - fail test" 15 | try: 16 | res=occ.download_describe("dog") 17 | except Exception as e: 18 | assert str(e) == "format not in list of acceptable formats" 19 | -------------------------------------------------------------------------------- /pygbif/species/name_parser.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import gbif_baseurl, gbif_POST 2 | 3 | 4 | def name_parser(name, **kwargs): 5 | """ 6 | Parse taxon names using the GBIF name parser 7 | 8 | :param name: [str] A character vector of scientific names. (required) 9 | 10 | reference: http://www.gbif.org/developer/species#parser 11 | 12 | Usage:: 13 | 14 | from pygbif import species 15 | species.name_parser('x Agropogon littoralis') 16 | species.name_parser(['Arrhenatherum elatius var. elatius', 17 | 'Secale cereale subsp. cereale', 'Secale cereale ssp. cereale', 18 | 'Vanessa atalanta (Linnaeus, 1758)']) 19 | """ 20 | url = gbif_baseurl + "parser/name" 21 | if name.__class__ == str: 22 | name = [name] 23 | return gbif_POST(url, name, **kwargs) 24 | -------------------------------------------------------------------------------- /test/test-wkt_rewind.py: -------------------------------------------------------------------------------- 1 | """Tests for utils module - wkt_rewind""" 2 | import pytest 3 | import unittest 4 | import re 5 | from pygbif import utils 6 | 7 | x = "POLYGON((144.6 13.2, 144.6 13.6, 144.9 13.6, 144.9 13.2, 144.6 13.2))" 8 | 9 | 10 | def test_wkt_rewind(): 11 | "utils.wkt_rewind - basic test" 12 | res = utils.wkt_rewind(x) 13 | assert str == res.__class__ 14 | 15 | 16 | class TestWKTClass(unittest.TestCase): 17 | def test_wkt_rewind_digits(self): 18 | "utils.wkt_rewind - digits works as expected" 19 | res = utils.wkt_rewind(x, digits=3) 20 | pat = re.compile("\\.[0-9]{3}\\s") 21 | out = pat.search(res) 22 | self.assertIsInstance(out.string, str) 23 | 24 | 25 | def test_wkt_rewind_fails_well(): 26 | "utils.wkt_rewind - fails well" 27 | with pytest.raises(TypeError): 28 | utils.wkt_rewind(x, digits="foo") 29 | -------------------------------------------------------------------------------- /test/test-registry-dataset_metrics.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - dataset_metrics method""" 2 | import vcr 3 | from pygbif import registry 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_dataset_metrics.yaml") 7 | def test_dataset_metrics(): 8 | "registry.dataset_metrics - basic test" 9 | res = registry.dataset_metrics(uuid="3f8a1297-3259-4700-91fc-acc4170b27ce") 10 | assert dict == res.__class__ 11 | assert 18 == len(res) 12 | 13 | 14 | @vcr.use_cassette("test/vcr_cassettes/test_dataset_metrics_multiple_uuids.yaml") 15 | def test_dataset_metrics_multiple_uuids(): 16 | "registry.dataset_metrics - multiple uuids" 17 | uuids = [ 18 | "3f8a1297-3259-4700-91fc-acc4170b27ce", 19 | "66dd0960-2d7d-46ee-a491-87b9adcfe7b1", 20 | ] 21 | res = registry.dataset_metrics(uuids) 22 | assert list == res.__class__ 23 | assert 2 == len(res) 24 | -------------------------------------------------------------------------------- /docs/modules/registry.rst: -------------------------------------------------------------------------------- 1 | .. _registry-modules: 2 | 3 | =============== 4 | registry module 5 | =============== 6 | 7 | registry module API: 8 | 9 | * `organizations` 10 | * `nodes` 11 | * `networks` 12 | * `installations` 13 | * `datasets` 14 | * `dataset_metrics` 15 | * `dataset_suggest` 16 | * `dataset_search` 17 | 18 | Example usage: 19 | 20 | .. code-block:: python 21 | 22 | from pygbif import registry 23 | registry.dataset_metrics(uuid='3f8a1297-3259-4700-91fc-acc4170b27ce') 24 | 25 | 26 | registry API 27 | ============ 28 | 29 | .. py:module:: pygbif 30 | :noindex: 31 | 32 | .. automethod:: registry.datasets 33 | .. automethod:: registry.dataset_metrics 34 | .. automethod:: registry.dataset_suggest 35 | .. automethod:: registry.dataset_search 36 | .. automethod:: registry.installations 37 | .. automethod:: registry.networks 38 | .. automethod:: registry.nodes 39 | .. automethod:: registry.organizations 40 | -------------------------------------------------------------------------------- /pygbif/species/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF taxonomic names APIs methods 3 | 4 | * `name_backbone`: Lookup names in the GBIF backbone taxonomy 5 | * `name_suggest`: Quick and simple autocomplete lookup service 6 | * `name_usage`: Lookup details for specific names in all taxonomies in GBIF 7 | * `name_lookup`: Lookup names in all taxonomies in GBIF 8 | * `name_parser`: Parse taxon names using the GBIF name parser 9 | 10 | If you are looking for behavior similar to the GBIF website when you search 11 | for a name, `name_backbone` may be what you want. For example, a search for 12 | *Lantanophaga pusillidactyla* on the GBIF website and with `name_backbone` 13 | will give back as a first result the correct name 14 | *Lantanophaga pusillidactylus*. 15 | """ 16 | 17 | from .name_suggest import name_suggest 18 | from .name_backbone import name_backbone 19 | from .name_lookup import name_lookup 20 | from .name_usage import name_usage 21 | from .name_parser import name_parser 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2020 Scott Chamberlain 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /test/test-occurrences-get.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - get methods""" 2 | import vcr 3 | from pygbif import occurrences 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_get.yaml") 7 | def test_get(): 8 | "occurrences.get - basic test" 9 | res = occurrences.get(key=142316497) 10 | assert "dict" == res.__class__.__name__ 11 | assert len(res) > 30 12 | assert 142316497 == res["key"] 13 | 14 | 15 | @vcr.use_cassette("test/vcr_cassettes/test_get_verbatim.yaml") 16 | def test_get_verbatim(): 17 | "occurrences.get_verbatim - basic test" 18 | res = occurrences.get_verbatim(key=142316497) 19 | assert "dict" == res.__class__.__name__ 20 | assert len(res) > 20 21 | assert 142316497 == res["key"] 22 | 23 | 24 | @vcr.use_cassette("test/vcr_cassettes/test_get_fragment.yaml") 25 | def test_get_fragment(): 26 | "occurrences.get_fragment - basic test" 27 | res = occurrences.get_fragment(key=1986620884) 28 | assert "dict" == res.__class__.__name__ 29 | assert "HumanObservation" == res["basisOfRecord"] 30 | -------------------------------------------------------------------------------- /test/test-registry-datasets.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - datasets method""" 2 | import vcr 3 | from pygbif import registry 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_datasets.yaml") 7 | def test_datasets(): 8 | "registry.datasets - basic test" 9 | res = registry.datasets() 10 | assert dict == res.__class__ 11 | 12 | 13 | @vcr.use_cassette("test/vcr_cassettes/test_datasets_limit.yaml") 14 | def test_datasets_limit(): 15 | "registry.datasets - limit param" 16 | res = registry.datasets(limit=1) 17 | assert dict == res.__class__ 18 | assert 1 == len(res["results"]) 19 | 20 | res = registry.datasets(limit=3) 21 | assert dict == res.__class__ 22 | assert 3 == len(res["results"]) 23 | 24 | 25 | @vcr.use_cassette("test/vcr_cassettes/test_datasets_type.yaml") 26 | def test_datasets_type(): 27 | "registry.datasets - type param" 28 | res = registry.datasets(type="OCCURRENCE") 29 | vv = [x["type"] for x in res["results"]] 30 | assert dict == res.__class__ 31 | assert 100 == len(res["results"]) 32 | assert "OCCURRENCE" == list(set(vv))[0] 33 | -------------------------------------------------------------------------------- /docs/docs/usecases.rst: -------------------------------------------------------------------------------- 1 | .. _usecases: 2 | 3 | Usecases 4 | ======== 5 | 6 | Use case 1: Get occurrence data for a set of taxonomic names 7 | ------------------------------------------------------------ 8 | 9 | Load library 10 | 11 | .. code-block:: python 12 | 13 | from pygbif import species as species 14 | from pygbif import occurrences as occ 15 | 16 | First, get GBIF backbone taxonomic keys 17 | 18 | .. code-block:: python 19 | 20 | splist = ['Cyanocitta stelleri', 'Junco hyemalis', 'Aix sponsa', 21 | 'Ursus americanus', 'Pinus conorta', 'Poa annuus'] 22 | keys = [ species.name_backbone(x)['usageKey'] for x in splist ] 23 | 24 | Then, get a count of occurrence records for each taxon, and pull out 25 | number of records found for each taxon 26 | 27 | .. code-block:: python 28 | 29 | out = [ occ.search(taxonKey = x, limit=0)['count'] for x in keys ] 30 | 31 | Make a dict of species names and number of records, sorting in 32 | descending order 33 | 34 | .. code-block:: python 35 | 36 | x = dict(zip(splist, out)) 37 | sorted(x.items(), key=lambda z:z[1], reverse=True) 38 | 39 | -------------------------------------------------------------------------------- /docs/license.rst: -------------------------------------------------------------------------------- 1 | .. _license: 2 | 3 | LICENSE 4 | ======= 5 | 6 | Copyright (C) 2019 Scott Chamberlain 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 11 | 12 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 13 | -------------------------------------------------------------------------------- /test/test-registry-networks.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - networks""" 2 | import vcr 3 | from pygbif import registry 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_networks.yaml") 7 | def test_networks(): 8 | "registry.networks - basic test" 9 | res = registry.networks() 10 | assert dict == res.__class__ 11 | assert 2 == len(res) 12 | assert len(res["data"]) >= 10 13 | assert ["data", "meta"] == sorted(res.keys()) 14 | 15 | @vcr.use_cassette("test/vcr_cassettes/test_networks_limit.yaml") 16 | def test_networks_limit(): 17 | "registry.networks - limit param " 18 | res = registry.networks(limit=5) 19 | assert dict == res.__class__ 20 | assert 5 == len(res["data"]) 21 | 22 | @vcr.use_cassette("test/vcr_cassettes/test_networks_uuid.yaml") 23 | def test_networks_uuid(): 24 | "registry.networks - with a uuid" 25 | res = registry.networks(uuid="2b7c7b4f-4d4f-40d3-94de-c28b6fa054a6") 26 | assert dict == res.__class__ 27 | assert 2 == len(res) 28 | assert str == res["data"]["title"].__class__ 29 | assert "modifiedBy" in res["data"].keys() 30 | -------------------------------------------------------------------------------- /test/test-registry-nodes.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - nodes methods""" 2 | import vcr 3 | 4 | from pygbif import registry 5 | 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_nodes.yaml") 8 | def test_nodes(): 9 | "registry.nodes - basic test" 10 | res = registry.nodes() 11 | assert dict == res.__class__ 12 | assert 2 == len(res) 13 | assert 100 == len(res["data"]) 14 | assert ["data", "meta"] == sorted(res.keys()) 15 | 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_nodes_limit.yaml") 18 | def test_nodes_limit(): 19 | "registry.nodes - limit param" 20 | res = registry.nodes(limit=5) 21 | assert dict == res.__class__ 22 | assert 5 == len(res["data"]) 23 | 24 | 25 | @vcr.use_cassette("test/vcr_cassettes/test_nodes_return.yaml") 26 | def test_nodes_return(): 27 | "registry.nodes - data param" 28 | res = registry.nodes(data="identifier", uuid="03e816b3-8f58-49ae-bc12-4e18b358d6d9") 29 | assert dict == res.__class__ 30 | assert 2 == len(res) 31 | assert 1 == len(res["data"]) 32 | assert 6 == len(res["data"][0]) 33 | assert "identifier" in res["data"][0].keys() 34 | -------------------------------------------------------------------------------- /test/test-species-name_usage.py: -------------------------------------------------------------------------------- 1 | """Tests for species module - name_usage methods""" 2 | import vcr 3 | from pygbif import species 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_name_usage.yaml") 7 | def test_name_usage(): 8 | "species.name_usage - basic test" 9 | res = species.name_usage(key=1) 10 | assert dict == res.__class__ 11 | assert 23 == len(res) 12 | assert 1 == res["key"] 13 | 14 | 15 | @vcr.use_cassette("test/vcr_cassettes/test_name_usage_paging.yaml") 16 | def test_name_usage_paging(): 17 | "species.name_usage - paging" 18 | res = species.name_usage(limit=10) 19 | assert dict == res.__class__ 20 | assert 4 == len(res) 21 | assert 10 == len(res["results"]) 22 | 23 | 24 | @vcr.use_cassette("test/vcr_cassettes/test_name_usage_datasetkey.yaml") 25 | def test_name_usage_datasetkey(): 26 | "species.name_usage - datasetkey works" 27 | res = species.name_usage(datasetKey="d7dddbf4-2cf0-4f39-9b2a-bb099caae36c") 28 | assert dict == res.__class__ 29 | assert 4 == len(res) 30 | assert ( 31 | "d7dddbf4-2cf0-4f39-9b2a-bb099caae36c" 32 | == list(set([x["datasetKey"] for x in res["results"]]))[0] 33 | ) 34 | -------------------------------------------------------------------------------- /test/test-registry-installations.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - installations methods""" 2 | import vcr 3 | 4 | from pygbif import registry 5 | 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_installations.yaml") 8 | def test_installations(): 9 | "registry.installations - basic test" 10 | res = registry.installations() 11 | assert dict == res.__class__ 12 | assert 2 == len(res) 13 | assert 100 == len(res["data"]) 14 | assert ["data", "meta"] == sorted(res.keys()) 15 | 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_installations_limit.yaml") 18 | def test_installations_limit(): 19 | "registry.installations - limit param " 20 | res = registry.installations(limit=5) 21 | assert dict == res.__class__ 22 | assert 5 == len(res["data"]) 23 | 24 | 25 | @vcr.use_cassette("test/vcr_cassettes/test_installations_uuid.yaml") 26 | def test_installations_uuid(): 27 | "registry.installations - with a uuid" 28 | res = registry.installations(uuid="b77901f9-d9b0-47fa-94e0-dd96450aa2b4") 29 | assert dict == res.__class__ 30 | assert 2 == len(res) 31 | assert 16 == len(res["data"]) 32 | assert dict == res["data"]["endpoints"][0].__class__ 33 | assert "identifiers" in res["data"].keys() 34 | -------------------------------------------------------------------------------- /.github/workflows/python.yml: -------------------------------------------------------------------------------- 1 | name: Python 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ['3.9', '3.10', '3.11'] 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | GBIF_USER: ${{ secrets.GBIF_USER }} 14 | GBIF_PWD: ${{ secrets.GBIF_PWD }} 15 | GBIF_EMAIL: ${{ secrets.GBIF_EMAIL }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install system dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install -y libgeos-c1v5 libgeos-dev 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 30 | - name: Install pygbif 31 | run: make 32 | - name: Tests 33 | run: pytest 34 | - name: Test coverage 35 | if: matrix.python-version == '3.10' 36 | run: | 37 | python3 -m "pytest" --cov-report=xml --cov=pygbif test/ 38 | codecov 39 | -------------------------------------------------------------------------------- /test/test-registry-organizations.py: -------------------------------------------------------------------------------- 1 | """Tests for registry module - organizations""" 2 | import vcr 3 | from pygbif import registry 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_organizations.yaml") 7 | def test_organizations(): 8 | "registry.organizations - basic test" 9 | res = registry.organizations() 10 | assert dict == res.__class__ 11 | assert 2 == len(res) 12 | assert 100 == len(res["data"]) 13 | assert ["data", "meta"] == sorted(res.keys()) 14 | 15 | 16 | @vcr.use_cassette("test/vcr_cassettes/test_organizations_limit.yaml") 17 | def test_organizations_limit(): 18 | "registry.organizations - limit param " 19 | res = registry.organizations(limit=5) 20 | assert dict == res.__class__ 21 | assert 5 == len(res["data"]) 22 | 23 | 24 | @vcr.use_cassette("test/vcr_cassettes/test_organizations_uuid.yaml") 25 | def test_organizations_uuid(): 26 | "registry.organizations - with a uuid" 27 | res = registry.organizations(uuid="e2e717bf-551a-4917-bdc9-4fa0f342c530") 28 | assert dict == res.__class__ 29 | assert 2 == len(res) 30 | assert list == res["data"]["contacts"].__class__ 31 | assert dict == res["data"]["contacts"][0].__class__ 32 | assert list == res["data"]["contacts"][0]["position"].__class__ 33 | assert "numPublishedDatasets" in res["data"].keys() 34 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_download_cancel.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: 'false' 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | Content-Length: 12 | - '5' 13 | Content-Type: 14 | - application/json 15 | user-agent: 16 | - python-requests/2.31.0,pygbif/0.6.6 17 | method: DELETE 18 | uri: http://api.gbif.org/v1/occurrence/download/request/0003970-140910143529206 19 | response: 20 | body: 21 | string: '' 22 | headers: 23 | Age: 24 | - '0' 25 | Cache-Control: 26 | - no-cache, no-store, max-age=0, must-revalidate 27 | Connection: 28 | - keep-alive 29 | Date: 30 | - Fri, 14 Nov 2025 10:42:23 GMT 31 | Expires: 32 | - '0' 33 | Pragma: 34 | - no-cache 35 | Vary: 36 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 37 | Via: 38 | - 1.1 varnish (Varnish/6.6) 39 | X-Content-Type-Options: 40 | - nosniff 41 | X-Frame-Options: 42 | - DENY 43 | X-Varnish: 44 | - '793610877' 45 | X-XSS-Protection: 46 | - '0' 47 | status: 48 | code: 204 49 | message: No Content 50 | version: 1 51 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/count?taxonKey=3329049 15 | response: 16 | body: 17 | string: '1265' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '662' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '4' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:15 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 766936326 717260329 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test-occurrences-download_citation.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | User-Agent: 12 | - python-requests/2.32.3 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/download/0235283-220831081235567/citation 15 | response: 16 | body: 17 | string: 'GBIF.org (2 January 2023) GBIF Occurrence Download https://doi.org/10.15468/dl.29wbtx 18 | 19 | ' 20 | headers: 21 | Accept-Ranges: 22 | - bytes 23 | Age: 24 | - '0' 25 | Cache-Control: 26 | - public, max-age=3603 27 | Connection: 28 | - keep-alive 29 | Content-Length: 30 | - '86' 31 | Content-Type: 32 | - application/json 33 | Date: 34 | - Fri, 04 Oct 2024 09:38:11 GMT 35 | Expires: 36 | - '0' 37 | Pragma: 38 | - no-cache 39 | Vary: 40 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 41 | Via: 42 | - 1.1 varnish (Varnish/6.0) 43 | X-Content-Type-Options: 44 | - nosniff 45 | X-Frame-Options: 46 | - DENY 47 | X-Varnish: 48 | - '851347368' 49 | X-XSS-Protection: 50 | - 1; mode=block 51 | status: 52 | code: 200 53 | message: OK 54 | version: 1 55 | -------------------------------------------------------------------------------- /pygbif/utils/wkt_rewind.py: -------------------------------------------------------------------------------- 1 | from geojson_rewind import rewind 2 | from geomet import wkt 3 | import decimal 4 | import statistics 5 | 6 | 7 | def wkt_rewind(x, digits=None): 8 | """ 9 | reverse WKT winding order 10 | 11 | :param x: [str] WKT string 12 | :param digits: [int] number of digits after decimal to use for the return string. 13 | by default, we use the mean number of digits in your string. 14 | 15 | :return: a string 16 | 17 | Usage:: 18 | 19 | from pygbif import wkt_rewind 20 | x = 'POLYGON((144.6 13.2, 144.6 13.6, 144.9 13.6, 144.9 13.2, 144.6 13.2))' 21 | wkt_rewind(x) 22 | wkt_rewind(x, digits = 0) 23 | wkt_rewind(x, digits = 3) 24 | wkt_rewind(x, digits = 7) 25 | """ 26 | z = wkt.loads(x) 27 | if digits is None: 28 | coords = z["coordinates"] 29 | nums = __flatten(coords) 30 | dec_n = [decimal.Decimal(str(w)).as_tuple().exponent for w in nums] 31 | digits = abs(statistics.mean(dec_n)) 32 | else: 33 | if not isinstance(digits, int): 34 | raise TypeError("'digits' must be an int") 35 | wound = rewind(z) 36 | back_to_wkt = wkt.dumps(wound, decimals=digits) 37 | return back_to_wkt 38 | 39 | 40 | # from https://stackoverflow.com/a/12472564/1091766 41 | def __flatten(S): 42 | if S == []: 43 | return S 44 | if isinstance(S[0], list): 45 | return __flatten(S[0]) + __flatten(S[1:]) 46 | return S[:1] + __flatten(S[1:]) 47 | -------------------------------------------------------------------------------- /docs/conduct.rst: -------------------------------------------------------------------------------- 1 | .. _conduct: 2 | 3 | Contributor Code of Conduct 4 | =========================== 5 | 6 | As contributors and maintainers of this project, we pledge to respect all people who 7 | contribute through reporting issues, posting feature requests, updating documentation, 8 | submitting pull requests or patches, and other activities. 9 | 10 | We are committed to making participation in this project a harassment-free experience for 11 | everyone, regardless of level of experience, gender, gender identity and expression, 12 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 13 | 14 | Examples of unacceptable behavior by participants include the use of sexual language or 15 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 16 | insults, or other unprofessional conduct. 17 | 18 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 19 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 20 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 21 | from the project team. 22 | 23 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 24 | opening an issue or contacting one or more of the project maintainers. 25 | 26 | This Code of Conduct is adapted from the Contributor Covenant 27 | (http:contributor-covenant.org), version 1.0.0, available at 28 | http://contributor-covenant.org/version/1/0/0/ 29 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_backbone_class.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v2/species/match?class=Insecta 15 | response: 16 | body: 17 | string: '{"usage":{"key":"216","name":"Insecta","canonicalName":"Insecta","rank":"CLASS","status":"ACCEPTED","type":"SCIENTIFIC","formattedName":"Insecta"},"classification":[{"key":"1","name":"Animalia","rank":"KINGDOM"},{"key":"54","name":"Arthropoda","rank":"PHYLUM"},{"key":"216","name":"Insecta","rank":"CLASS"}],"diagnostics":{"matchType":"EXACT","confidence":100,"timeTaken":19,"timings":{"nameNRank":0,"sciNameMatch":20,"nameParse":0,"luceneMatch":20}},"synonym":false}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '0' 23 | Cache-Control: 24 | - public, max-age=3601 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '474' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 12:22:07 GMT 33 | Vary: 34 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 35 | Via: 36 | - 1.1 varnish (Varnish/6.6) 37 | X-Varnish: 38 | - '922845360' 39 | status: 40 | code: 200 41 | message: OK 42 | version: 1 43 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count_year.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/counts/year?year=1990%2C2000 15 | response: 16 | body: 17 | string: '{"2000":25433414,"1999":24122435,"1998":20129517,"1997":19678537,"1995":18729666,"1996":18640505,"1994":16234175,"1993":15952253,"1992":14960631,"1991":14022428,"1990":13262483}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '661' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '177' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:18 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 764183137 624499026 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_nodes_return.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/node/03e816b3-8f58-49ae-bc12-4e18b358d6d9/identifier?limit=100 15 | response: 16 | body: 17 | string: '[{"key":13657,"type":"GBIF_PARTICIPANT","identifier":"308","createdBy":"registry-migration.gbif.org","created":"2013-10-24T09:06:08.312+00:00","primary":false}]' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '0' 23 | Cache-Control: 24 | - public, max-age=3601 25 | Connection: 26 | - keep-alive 27 | Content-Type: 28 | - application/json 29 | Date: 30 | - Fri, 14 Nov 2025 10:13:45 GMT 31 | Expires: 32 | - '0' 33 | Pragma: 34 | - no-cache 35 | Transfer-Encoding: 36 | - chunked 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - '794100253' 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count_basisofrecord.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/counts/basisOfRecord 15 | response: 16 | body: 17 | string: '{"HUMAN_OBSERVATION":3129337649,"PRESERVED_SPECIMEN":270543758,"MATERIAL_SAMPLE":147286014,"OCCURRENCE":50282110,"MACHINE_OBSERVATION":38711382,"OBSERVATION":16762802,"FOSSIL_SPECIMEN":9829806,"MATERIAL_CITATION":8659611,"LIVING_SPECIMEN":3466300}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '661' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '247' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:17 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 814612517 673323170 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/test-species-name_lookup.py: -------------------------------------------------------------------------------- 1 | """Tests for species module - name_lookup methods""" 2 | import vcr 3 | from pygbif import species 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_name_lookup.yaml") 7 | def test_name_lookup(): 8 | "species.name_lookup - basic test" 9 | res = species.name_lookup(q="mammalia") 10 | assert dict == res.__class__ 11 | assert 6 == len(res) 12 | assert 100 == len(res["results"]) 13 | assert "Mammaliaformes" == res["results"][0]["canonicalName"] 14 | 15 | 16 | @vcr.use_cassette("test/vcr_cassettes/test_name_lookup_paging.yaml") 17 | def test_name_lookup_paging(): 18 | "species.name_lookup - paging" 19 | res = species.name_lookup(q="mammalia", limit=1) 20 | assert dict == res.__class__ 21 | assert 6 == len(res) 22 | assert 1 == len(res["results"]) 23 | 24 | 25 | @vcr.use_cassette("test/vcr_cassettes/test_name_lookup_rank.yaml") 26 | def test_name_lookup_rank(): 27 | "species.name_lookup - rank parameter" 28 | res = species.name_lookup("Helianthus annuus", rank="species", limit=10) 29 | assert dict == res.__class__ 30 | assert 10 == len(res["results"]) 31 | assert "SPECIES" == list(set([x["rank"] for x in res["results"]]))[0] 32 | 33 | 34 | @vcr.use_cassette("test/vcr_cassettes/test_name_lookup_faceting.yaml") 35 | def test_name_lookup_faceting(): 36 | "species.name_lookup - faceting" 37 | res = species.name_lookup(facet="status", limit=0) 38 | assert dict == res.__class__ 39 | assert 6 == len(res) 40 | assert 0 == len(res["results"]) 41 | assert 1 == len(res["facets"]) 42 | assert 2 == len(res["facets"][0]) 43 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_lookup_faceting.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/species/search?limit=0&facet=status&hl=false&verbose=false 15 | response: 16 | body: 17 | string: '{"offset":0,"limit":0,"endOfRecords":false,"count":52777960,"results":[],"facets":[{"field":"STATUS","counts":[{"name":"ACCEPTED","count":33105798},{"name":"SYNONYM","count":17673611},{"name":"DOUBTFUL","count":959382},{"name":"HETEROTYPIC_SYNONYM","count":512848},{"name":"HOMOTYPIC_SYNONYM","count":460907},{"name":"MISAPPLIED","count":38459},{"name":"PROPARTE_SYNONYM","count":19944}]}]}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '0' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '390' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:26:12 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - '793969568' 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /pygbif/occurrences/get.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import gbif_baseurl, gbif_GET 2 | 3 | 4 | def get(key, **kwargs): 5 | """ 6 | Gets details for a single, interpreted occurrence 7 | 8 | :param key: [int] A GBIF occurrence key 9 | 10 | :return: A dictionary, of results 11 | 12 | Usage:: 13 | 14 | from pygbif import occurrences 15 | occurrences.get(key = 1258202889) 16 | occurrences.get(key = 1227768771) 17 | occurrences.get(key = 1227769518) 18 | """ 19 | url = gbif_baseurl + "occurrence/" + str(key) 20 | out = gbif_GET(url, {}, **kwargs) 21 | return out 22 | 23 | 24 | def get_verbatim(key, **kwargs): 25 | """ 26 | Gets a verbatim occurrence record without any interpretation 27 | 28 | :param key: [int] A GBIF occurrence key 29 | 30 | :return: A dictionary, of results 31 | 32 | Usage:: 33 | 34 | from pygbif import occurrences 35 | occurrences.get_verbatim(key = 1258202889) 36 | occurrences.get_verbatim(key = 1227768771) 37 | occurrences.get_verbatim(key = 1227769518) 38 | """ 39 | url = gbif_baseurl + "occurrence/" + str(key) + "/verbatim" 40 | out = gbif_GET(url, {}, **kwargs) 41 | return out 42 | 43 | 44 | def get_fragment(key, **kwargs): 45 | """ 46 | Get a single occurrence fragment in its raw form (xml or json) 47 | 48 | :param key: [int] A GBIF occurrence key 49 | 50 | :return: A dictionary, of results 51 | 52 | Usage:: 53 | 54 | from pygbif import occurrences 55 | occurrences.get_fragment(key = 1052909293) 56 | occurrences.get_fragment(key = 1227768771) 57 | occurrences.get_fragment(key = 1227769518) 58 | """ 59 | url = gbif_baseurl + "occurrence/" + str(key) + "/fragment" 60 | out = gbif_GET(url, {}, **kwargs) 61 | return out 62 | -------------------------------------------------------------------------------- /.github/workflows/python_live_tests.yml: -------------------------------------------------------------------------------- 1 | name: Python live API tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ['3.11'] 11 | env: 12 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 13 | GBIF_USER: ${{ secrets.GBIF_USER }} 14 | GBIF_PWD: ${{ secrets.GBIF_PWD }} 15 | GBIF_EMAIL: ${{ secrets.GBIF_EMAIL }} 16 | steps: 17 | - uses: actions/checkout@v4 18 | - name: Set up Python ${{ matrix.python-version }} 19 | uses: actions/setup-python@v4 20 | with: 21 | python-version: ${{ matrix.python-version }} 22 | - name: Install system dependencies 23 | run: | 24 | sudo apt-get update 25 | sudo apt-get install -y libgeos-c1v5 libgeos-dev 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip 29 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 30 | 31 | - name: Delete vcr cassettes so that it runs live tests 32 | run: | 33 | rm -rf test/vcr_cassettes/* 34 | if [ "$(ls -A test/vcr_cassettes)" ]; then 35 | echo "Directory is not empty" 36 | exit 1 37 | fi 38 | 39 | - name: Tests 40 | run: pytest 41 | 42 | - name: Check if vcr cassettes were created today 43 | run: | 44 | for file in test/vcr_cassettes/*; do 45 | file_date=$(stat -c %y "$file" | cut -d ' ' -f 1) 46 | echo "File $file was created on $file_date" 47 | current_date=$(date +%Y-%m-%d) 48 | if [[ "$file_date" != "$current_date" ]]; then 49 | echo "File $file was not created today" 50 | exit 1 51 | fi 52 | done 53 | 54 | -------------------------------------------------------------------------------- /pygbif/occurrences/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | GBIF occurrences APIs methods 3 | 4 | * `search`: Search GBIF occurrences 5 | * `get`: Gets details for a single, interpreted occurrence 6 | * `get_verbatim`: Gets a verbatim occurrence record without any interpretation 7 | * `get_fragment`: Get a single occurrence fragment in its raw form (xml or json) 8 | * `count`: Returns occurrence counts for a predefined set of dimensions 9 | * `count_basisofrecord`: Lists occurrence counts by basis of record 10 | * `count_year`: Lists occurrence counts by year 11 | * `count_datasets`: Lists occurrence counts for datasets that cover a given taxon or country 12 | * `count_countries`: Lists occurrence counts for all countries covered by the data published by the given country 13 | * `count_schema`: List the supported metrics by the service 14 | * `count_publishingcountries`: Lists occurrence counts for all countries that publish data about the given country 15 | * `download`: Spin up a download request for GBIF occurrence data 16 | * `download_meta`: Retrieve occurrence download metadata by unique download key 17 | * `download_list`: Lists the downloads created by a user 18 | * `download_get`: Get a download from GBIF 19 | * `download_cancel`: Cancel a download from GBIF 20 | * `citation`: Get citation from a download key 21 | """ 22 | 23 | from .search import search 24 | from .get import get, get_verbatim, get_fragment 25 | from .count import ( 26 | count, 27 | count_basisofrecord, 28 | count_year, 29 | count_datasets, 30 | count_countries, 31 | count_schema, 32 | count_publishingcountries, 33 | ) 34 | from .download import ( 35 | download, 36 | download_meta, 37 | download_list, 38 | download_get, 39 | download_cancel, 40 | download_describe, 41 | download_sql, 42 | download_citation 43 | ) 44 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. _contributing: 2 | 3 | Contributing 4 | ============ 5 | 6 | 7 | Bug reports 8 | ----------- 9 | 10 | Please report bug reports on our `issue tracker`_. 11 | 12 | .. _issue tracker: https://github.com/gbif/pygbif/issues 13 | 14 | 15 | Feature requests 16 | ---------------- 17 | 18 | Please put feature requests on our `issue tracker`_. 19 | 20 | 21 | Pull requests 22 | ------------- 23 | 24 | When you submit a PR you'll see a template that pops up - it's reproduced 25 | here. 26 | 27 | 28 | - Provide a general summary of your changes in the Title 29 | - Describe your changes in detail 30 | - If the PR closes an issue make sure include e.g., `fix #4` or similar, 31 | or if just relates to an issue make sure to mention it like `#4` 32 | - If introducing a new feature or changing behavior of existing 33 | methods/functions, include an example if possible to do in brief form 34 | - Did you remember to include tests? Unless you're changing docs/grammar, 35 | please include new tests for your change 36 | 37 | 38 | Writing tests 39 | ------------- 40 | 41 | We're using `nose` for testing. See the `nose docs`_ for help on 42 | contributing to or writing tests. 43 | 44 | Before running tests for the first time, you'll need install pygbif 45 | dependencies, but also nose and a couple other packages: 46 | 47 | .. code-block:: shell 48 | 49 | $ pip install -e . 50 | $ pip install nose vcrpy coverage 51 | 52 | The Makefile has a task for testing under Python 3: 53 | 54 | .. code-block:: shell 55 | 56 | $ make test 57 | 58 | .. _nose docs: http://nose.readthedocs.io/en/latest/ 59 | 60 | Code formatting 61 | --------------- 62 | 63 | We're using the `Black`_ formatter, so make sure you use that before 64 | submitting code - there's lots of text editor integrations, a command 65 | line tool, etc. 66 | 67 | .. _Black: https://github.com/psf/black 68 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_usage.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/species/1?limit=100 15 | response: 16 | body: 17 | string: '{"key":1,"nubKey":1,"nameKey":130188353,"taxonID":"gbif:1","sourceTaxonKey":1,"kingdom":"Animalia","kingdomKey":1,"datasetKey":"d7dddbf4-2cf0-4f39-9b2a-bb099caae36c","constituentKey":"d7dddbf4-2cf0-4f39-9b2a-bb099caae36c","scientificName":"Animalia","canonicalName":"Animalia","vernacularName":"Animals","authorship":"","nameType":"SCIENTIFIC","rank":"KINGDOM","origin":"SOURCE","taxonomicStatus":"ACCEPTED","nomenclaturalStatus":[],"remarks":"","numDescendants":2981931,"lastCrawled":"2023-08-22T23:20:59.545+00:00","lastInterpreted":"2023-08-22T22:11:51.237+00:00","issues":[]}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '1392' 23 | Cache-Control: 24 | - public, max-age=3601 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '579' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:03:01 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 808159042 728040720 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/test-occurrences-search.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - search methods""" 2 | import vcr 3 | from pygbif import occurrences 4 | 5 | keyz = ["count", "facets", "results", "endOfRecords", "limit", "offset"] 6 | x = "https://orcid.org/0000-0003-1691-239X" 7 | 8 | 9 | @vcr.use_cassette("test/vcr_cassettes/test_search.yaml") 10 | def test_search(): 11 | "occurrences.search - basic test" 12 | res = occurrences.search(taxonKey=3329049) 13 | assert "dict" == res.__class__.__name__ 14 | assert 6 == len(res) 15 | assert sorted(keyz) == sorted(res.keys()) 16 | 17 | 18 | @vcr.use_cassette("test/vcr_cassettes/test_search_key1.yaml") 19 | def test_search_key1(): 20 | "occurrences.search - diff taxonKey" 21 | res = occurrences.search(taxonKey=2431762) 22 | assert "dict" == res.__class__.__name__ 23 | assert 6 == len(res) 24 | assert 2431762 == res["results"][0]["taxonKey"] 25 | 26 | 27 | @vcr.use_cassette("test/vcr_cassettes/test_search_key2.yaml") 28 | def test_search_key2(): 29 | "occurrences.search - diff taxonKey2" 30 | res = occurrences.search(taxonKey=2683264) 31 | assert "dict" == res.__class__.__name__ 32 | assert 6 == len(res) 33 | assert 2683264 == res["results"][0]["taxonKey"] 34 | 35 | 36 | @vcr.use_cassette("test/vcr_cassettes/test_search_recorded_by_id.yaml") 37 | def test_search_recorded_by_id(): 38 | "occurrences.search - recordedByID" 39 | res = occurrences.search(recordedByID=x, limit=3) 40 | assert "dict" == res.__class__.__name__ 41 | assert 6 == len(res) 42 | assert x == res["results"][0]["recordedByIDs"][0]["value"] 43 | 44 | 45 | @vcr.use_cassette("test/vcr_cassettes/test_search_identified_by_id.yaml") 46 | def test_search_identified_by_id(): 47 | "occurrences.search - identifiedByID" 48 | res = occurrences.search(identifiedByID=x, limit=3) 49 | assert "dict" == res.__class__.__name__ 50 | assert 6 == len(res) 51 | assert x == res["results"][0]["identifiedByIDs"][0]["value"] 52 | -------------------------------------------------------------------------------- /pygbif/__init__.py: -------------------------------------------------------------------------------- 1 | # pygbif 2 | 3 | """ 4 | pygbif library 5 | ~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | pygbif is a Python client for the Global Biodiversity Information Facility (GBIF) API. 8 | 9 | Usage:: 10 | 11 | # Import entire library 12 | import pygbif 13 | # or import modules as needed 14 | ## occurrences 15 | from pygbif import occurrences 16 | ## species 17 | from pygbif import species 18 | ## registry 19 | from pygbif import registry 20 | 21 | ## use advanced logging 22 | ### setup first 23 | import requests 24 | import logging 25 | import http.client 26 | http.client.HTTPConnection.debuglevel = 1 27 | logging.basicConfig() 28 | logging.getLogger().setLevel(logging.DEBUG) 29 | requests_log = logging.getLogger("requests.packages.urllib3") 30 | requests_log.setLevel(logging.DEBUG) 31 | requests_log.propagate = True 32 | ### then make request 33 | from pygbif import occurrences 34 | occurrences.search(geometry='POLYGON((30.1 10.1, 10 20, 20 40, 40 40, 30.1 10.1))', limit=20) 35 | """ 36 | 37 | from .package_metadata import __author__, __license__, __version__, __title__ 38 | from .occurrences import search, get, count, download 39 | from .species import name_parser, name_suggest, name_backbone, name_lookup, name_usage 40 | from .registry import datasets, nodes, networks, organizations, installations 41 | from .maps import map, GbifMap 42 | from .gbifissues import occ_issues_lookup 43 | from .utils import * 44 | from .caching import caching 45 | from .literature import search 46 | from .collection import search 47 | from .institution import search 48 | 49 | # Set default logging handler to avoid "No handler found" warnings. 50 | import logging 51 | from logging import NullHandler 52 | 53 | logging.getLogger(__name__).addHandler(NullHandler()) 54 | logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO) 55 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | from setuptools import setup 3 | from setuptools import find_packages 4 | 5 | with codecs.open("README.rst", "r", "utf-8") as f: 6 | readme = f.read().replace("\r", '') 7 | 8 | with codecs.open("Changelog.rst", "r", "utf-8") as f: 9 | changes = f.read().replace("\r", '') 10 | changes = changes.replace(":issue:", "") 11 | long_description = readme + "\n\n" + changes 12 | 13 | setup( 14 | name="pygbif", 15 | version="0.6.6", 16 | description="Python client for GBIF", 17 | long_description=long_description, 18 | long_description_content_type="text/x-rst", 19 | author="Scott Chamberlain", 20 | author_email="myrmecocystus@gmail.com", 21 | url="http://github.com/gbif/pygbif", 22 | download_url="https://github.com/gbif/pygbif/archive/refs/tags/v0.6.6.tar.gz", 23 | license="MIT", 24 | packages=find_packages(exclude=["test-*"]), 25 | install_requires=[ 26 | "requests>2.7", 27 | "requests-cache", 28 | "geojson_rewind", 29 | "geomet", 30 | "appdirs>=1.4.3", 31 | "matplotlib", 32 | ], 33 | classifiers=[ 34 | "Development Status :: 5 - Production/Stable", 35 | "Intended Audience :: Science/Research", 36 | "Topic :: Scientific/Engineering :: Bio-Informatics", 37 | "Natural Language :: English", 38 | "License :: OSI Approved :: MIT License", 39 | "Programming Language :: Python", 40 | "Programming Language :: Python :: 3.5", 41 | "Programming Language :: Python :: 3.6", 42 | "Programming Language :: Python :: 3.7", 43 | "Programming Language :: Python :: 3.8", 44 | "Programming Language :: Python :: 3.9", 45 | "Programming Language :: Python :: 3.10", 46 | "Programming Language :: Python :: 3.11", 47 | "Programming Language :: Python :: 3.12" 48 | ], 49 | keywords = ['gbif', 'biodiversity', 'specimens', 'API', 'web-services', 'occurrences', 'species', 'taxonomy'], 50 | ) 51 | -------------------------------------------------------------------------------- /test/test-species-name-backbone.py: -------------------------------------------------------------------------------- 1 | """Tests for species module - name_usage methods""" 2 | import vcr 3 | from pygbif import species 4 | 5 | 6 | @vcr.use_cassette("test/vcr_cassettes/test_name_backbone.yaml") 7 | def test_name_backbone(): 8 | "species.name_backbone - basic test" 9 | res = species.name_backbone(scientificName="Calopteryx splendens") 10 | assert dict == res.__class__ 11 | assert 5 == len(res) 12 | assert "Calopteryx splendens (Harris, 1780)" == res["usage"]["name"] 13 | assert list(res.keys()) == ['usage', 'classification', 'diagnostics', 'additionalStatus', 'synonym'] 14 | 15 | @vcr.use_cassette("test/vcr_cassettes/test_name_backbone_verbose.yaml") 16 | def test_name_backbone_verbose(): 17 | "species.name_backbone - verbose test" 18 | res = species.name_backbone(scientificName="Calopteryx", verbose=True) 19 | assert dict == res.__class__ 20 | assert list(res.keys()) == ['diagnostics', 'synonym'] 21 | assert list(res["diagnostics"]) == ['matchType', 'issues', 'confidence', 'note', 'timeTaken', 'alternatives', 'timings'] 22 | assert len(res["diagnostics"]["alternatives"]) > 5 23 | 24 | @vcr.use_cassette("test/vcr_cassettes/test_name_backbone_class.yaml") 25 | def test_name_backbone_class(): 26 | "species.name_backbone - class test" 27 | res = species.name_backbone(class_="Insecta") 28 | assert dict == res.__class__ 29 | assert 4 == len(res) 30 | assert "Insecta" == res["usage"]["name"] 31 | assert list(res.keys()) == ['usage', 'classification', 'diagnostics', 'synonym'] 32 | 33 | @vcr.use_cassette("test/vcr_cassettes/test_name_backbone_checklistKey.yaml") 34 | def test_name_backbone_checklistKey(): 35 | "species.name_backbone - checklistKey test" 36 | res = species.name_backbone(scientificName="Calopteryx splendens", checklistKey="7ddf754f-d193-4cc9-b351-99906754a03b") 37 | assert dict == res.__class__ 38 | assert res["usage"]["key"] == "Q2M4" 39 | assert 5 == len(res) 40 | assert "Calopteryx splendens" == res["usage"]["canonicalName"] 41 | assert list(res.keys()) == ['usage', 'classification', 'diagnostics', 'additionalStatus', 'synonym'] 42 | 43 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # Release process 2 | 3 | _This is only of interest to the project maintainers._ 4 | 5 | This is based on the documentation at https://setuptools.pypa.io/en/latest/userguide/quickstart.html and https://packaging.python.org/en/latest/tutorials/packaging-projects/. 6 | 7 | 1. Install necessary tools and configure PyPI credentials 8 | 9 | - `pip3 install --upgrade setuptools build twine` 10 | - edit `~/.pypirc` 11 | ``` 12 | [pypi] 13 | username = __token__ 14 | password = 15 | 16 | [testpypi] 17 | username = __token__ 18 | password = 19 | ``` 20 | 21 | 2. Prepare the release 22 | 23 | - Update `Changelog.rst` with a new section describing the changes in this release 24 | - Change the version number in `setup.py`. It is in the version line and the download_url line. 25 | - Change the version number in `pygbif/package_metadata.py` 26 | - For a trial run, just use a version like "0.6.1.dev1". 27 | - Otherwise, commit and push the changes to both files. 28 | 29 | 3. Build and verify the package 30 | 31 | - `rm -rf dist && python3 -m build` 32 | - `python3 -m twine check dist/*` 33 | 34 | 4. Upload to PyPI Test: 35 | 36 | - `python3 -m twine upload --repository testpypi dist/*` 37 | 38 | 5. Test the package in an empty virtual environment 39 | 40 | - Wait 2-3 minutes for test.pypi.org to update 41 | - `python3 -m venv test20220623-a` 42 | - `source test20220623-a/bin/activate` 43 | - `pip install --index-url https://test.pypi.org/simple/ --no-deps pygbif==0.6.1.dev1` 44 | (Installs only the pygbif release.) 45 | - `pip install pygbif==0.6.1.dev1` 46 | (Installs dependencies.) 47 | - `python` 48 | - `from pygbif import registry` 49 | - `registry.dataset_metrics(uuid='3f8a1297-3259-4700-91fc-acc4170b27ce')` 50 | 51 | (Run other checks as required, e.g. any new or changed functionality.) 52 | 53 | 6. Don't rebuild the package, but upload what was already built to pypi.org: 54 | 55 | - `python3 -m twine upload --repository pypi dist/*` 56 | 57 | 7. Create a release on GitHub 58 | 59 | - Go to https://github.com/gbif/pygbif/releases and create a release, using a tag matching the `download_url` from step 2, e.g. `v0.6.1`. 60 | -------------------------------------------------------------------------------- /test/test-literature-search.py: -------------------------------------------------------------------------------- 1 | """tests for literature search""" 2 | import vcr 3 | from pygbif import literature 4 | 5 | keyz = ["count", "facets", "results", "endOfRecords", "limit", "offset"] 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_literature_search.yaml") 8 | def test_search(): 9 | "literature.search - basic test" 10 | res = literature.search(limit=10) 11 | assert "dict" == res.__class__.__name__ 12 | assert 6 == len(res) 13 | assert sorted(keyz) == sorted(res.keys()) 14 | assert 10 == len(res["results"]) 15 | assert res["count"] >= 48132 # unlikely to go down 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_literature_search_key.yaml") 18 | def test_search_key(): 19 | "literature.search - key" 20 | res = literature.search(gbifDownloadKey="0235283-220831081235567") 21 | assert "dict" == res.__class__.__name__ 22 | assert 6 == len(res) 23 | assert sorted(keyz) == sorted(res.keys()) 24 | assert 1 == len(res["results"]) 25 | assert 2024 == res["results"][0]["year"] 26 | assert ["0235283-220831081235567","0026791-240906103802322"] == res["results"][0]["gbifDownloadKey"] 27 | 28 | @vcr.use_cassette("test/vcr_cassettes/test_literature_search_year.yaml") 29 | def test_search_year(): 30 | "literature.search - year" 31 | res = literature.search(year="2010,2024", limit=10) 32 | assert "dict" == res.__class__.__name__ 33 | assert 6 == len(res) 34 | assert sorted(keyz) == sorted(res.keys()) 35 | assert 10 == len(res["results"]) 36 | for result in res["results"]: 37 | assert 2010 <= result["year"] <= 2024 38 | 39 | @vcr.use_cassette("test/vcr_cassettes/test_literature_search_facet.yaml") 40 | def test_search_facet(): 41 | "literature.search - facet" 42 | res = literature.search(facet="year", limit=10) 43 | assert "dict" == res.__class__.__name__ 44 | assert 6 == len(res) 45 | assert sorted(keyz) == sorted(res.keys()) 46 | assert 10 == len(res["results"]) 47 | assert "list" == res["facets"].__class__.__name__ 48 | assert 2 == len(res["facets"][0]["counts"][0]) 49 | assert set(['name', 'count']) == set(res["facets"][0]["counts"][0].keys()) 50 | 51 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_backbone.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v2/species/match?scientificName=Calopteryx+splendens 15 | response: 16 | body: 17 | string: '{"usage":{"key":"1427067","name":"Calopteryx splendens (Harris, 1780)","canonicalName":"Calopteryx 18 | splendens","authorship":"(Harris, 1780)","rank":"SPECIES","code":"ZOOLOGICAL","status":"ACCEPTED","genericName":"Calopteryx","specificEpithet":"splendens","type":"SCIENTIFIC","formattedName":"Calopteryx 19 | splendens (Harris, 1780)"},"classification":[{"key":"1","name":"Animalia","rank":"KINGDOM"},{"key":"54","name":"Arthropoda","rank":"PHYLUM"},{"key":"216","name":"Insecta","rank":"CLASS"},{"key":"789","name":"Odonata","rank":"ORDER"},{"key":"4211","name":"Calopterygidae","rank":"FAMILY"},{"key":"1427007","name":"Calopteryx","rank":"GENUS"},{"key":"1427067","name":"Calopteryx 20 | splendens","rank":"SPECIES"}],"diagnostics":{"matchType":"EXACT","confidence":99,"timeTaken":8,"timings":{"nameNRank":0,"sciNameMatch":8,"nameParse":1,"luceneMatch":7}},"additionalStatus":[{"clbDatasetKey":"53131","datasetAlias":"IUCN","datasetKey":"19491596-35ae-4a91-9a98-85cf505f1bd3","status":"LEAST_CONCERN","statusCode":"LC","sourceId":"158701"}],"synonym":false}' 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '84' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Length: 31 | - '1062' 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Fri, 14 Nov 2025 12:20:43 GMT 36 | Vary: 37 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 38 | Via: 39 | - 1.1 varnish (Varnish/6.6) 40 | X-Varnish: 41 | - 941424663 898861727 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_networks_uuid.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/network/2b7c7b4f-4d4f-40d3-94de-c28b6fa054a6?limit=100 15 | response: 16 | body: 17 | string: '{"key":"2b7c7b4f-4d4f-40d3-94de-c28b6fa054a6","title":"Ocean Biodiversity 18 | Information System (OBIS)","description":"The Ocean Biodiversity Information 19 | System (OBIS) is a global open-access data and information clearing-house 20 | on marine biodiversity for science, conservation and sustainable development.","language":"eng","numConstituents":3282,"email":[],"phone":[],"homepage":["https://obis.org"],"logoUrl":"https://obis.org/images/logo.png","address":[],"createdBy":"ADMIN","modifiedBy":"MattBlissett","created":"2007-04-03T08:31:37.000+00:00","modified":"2020-06-17T11:34:47.474+00:00","contacts":[],"endpoints":[],"machineTags":[{"key":7598741,"namespace":"registry.gbif.org","name":"visibleOnDatasetPage","value":"true","createdBy":"MattBlissett","created":"2021-01-20T15:54:18.923+00:00"}],"tags":[],"identifiers":[],"comments":[]}' 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '0' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Type: 31 | - application/json 32 | Date: 33 | - Fri, 14 Nov 2025 10:13:40 GMT 34 | Expires: 35 | - '0' 36 | Pragma: 37 | - no-cache 38 | Transfer-Encoding: 39 | - chunked 40 | Vary: 41 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 42 | Via: 43 | - 1.1 varnish (Varnish/6.6) 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Frame-Options: 47 | - DENY 48 | X-Varnish: 49 | - '624731953' 50 | X-XSS-Protection: 51 | - 1; mode=block 52 | status: 53 | code: 200 54 | message: OK 55 | version: 1 56 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count_publishingcountries.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/counts/publishingCountries?country=DE 15 | response: 16 | body: 17 | string: '{"GERMANY":58308282,"NETHERLANDS":6749714,"UNITED_STATES":3609813,"BELGIUM":2456170,"UNITED_KINGDOM":2181272,"FRANCE":1921434,"DENMARK":1515099,"UNKNOWN":338012,"SWITZERLAND":262404,"SWEDEN":147806,"AUSTRIA":129858,"ESTONIA":119247,"POLAND":72316,"FINLAND":66361,"COLOMBIA":58178,"LUXEMBOURG":49177,"AUSTRALIA":27162,"NORWAY":25234,"SPAIN":20222,"CANADA":19365,"BRAZIL":11944,"JAPAN":9435,"NEW_ZEALAND":9067,"CZECH_REPUBLIC":7862,"RUSSIAN_FEDERATION":7798,"CHINA":5419,"PORTUGAL":4876,"SOUTH_AFRICA":4659,"ITALY":3918,"BULGARIA":3721,"MEXICO":3127,"ECUADOR":2388,"ARGENTINA":2197,"ISRAEL":1849,"PANAMA":1227,"UKRAINE":1122,"TAIWAN":858,"COSTA_RICA":836,"CHILE":453,"GREECE":281,"NICARAGUA":232,"HUNGARY":145,"URUGUAY":129,"LITHUANIA":128,"GHANA":73,"LATVIA":65,"VENEZUELA":63,"PERU":31,"KOREA_SOUTH":24,"ARMENIA":14,"TAJIKISTAN":7,"MOROCCO":7,"GUATEMALA":7,"NIGERIA":4,"ANTARCTICA":3,"TRINIDAD_TOBAGO":2,"SLOVENIA":2,"ZIMBABWE":1}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '661' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '930' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:20 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 766279736 719652357 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /docs/modules/occurrence.rst: -------------------------------------------------------------------------------- 1 | .. _occurrence-modules: 2 | 3 | ================= 4 | occurrence module 5 | ================= 6 | 7 | occurrence module API: 8 | 9 | * `search` 10 | * `get` 11 | * `get_verbatim` 12 | * `get_fragment` 13 | * `count` 14 | * `count_basisofrecord` 15 | * `count_year` 16 | * `count_datasets` 17 | * `count_countries` 18 | * `count_schema` 19 | * `count_publishingcountries` 20 | * `download` 21 | * `download_meta` 22 | * `download_list` 23 | * `download_get` 24 | * `download_sql` 25 | * `download_describe` 26 | * `download_citation` 27 | 28 | Example usage: 29 | 30 | .. code-block:: python 31 | 32 | from pygbif import occurrences as occ 33 | occ.search(taxonKey = 3329049) 34 | occ.get(key = 1986559641) 35 | occ.count(isGeoreferenced = True) 36 | occ.download('basisOfRecord = PRESERVED_SPECIMEN') 37 | occ.download('taxonKey = 3119195') 38 | occ.download('decimalLatitude > 50') 39 | occ.download_list(user = "sckott", limit = 5) 40 | occ.download_meta(key = "0000099-140929101555934") 41 | occ.download_get("0000066-140928181241064") 42 | occ.download_sql("SELECT datasetKey, countryCode, COUNT(*) FROM occurrence WHERE continent = 'EUROPE' GROUP BY datasetKey, countryCode") 43 | occ.download_describe("simpleCsv") 44 | occ.download_citation("0002526-241107131044228") 45 | 46 | 47 | occurrences API 48 | =============== 49 | 50 | 51 | .. py:module:: pygbif 52 | 53 | .. automethod:: occurrences.search 54 | .. automethod:: occurrences.get 55 | .. automethod:: occurrences.get_verbatim 56 | .. automethod:: occurrences.get_fragment 57 | .. automethod:: occurrences.count 58 | .. automethod:: occurrences.count_basisofrecord 59 | .. automethod:: occurrences.count_year 60 | .. automethod:: occurrences.count_datasets 61 | .. automethod:: occurrences.count_countries 62 | .. automethod:: occurrences.count_schema 63 | .. automethod:: occurrences.count_publishingcountries 64 | .. automethod:: occurrences.download 65 | .. automethod:: occurrences.download_meta 66 | .. automethod:: occurrences.download_list 67 | .. automethod:: occurrences.download_get 68 | .. automethod:: occurrences.download_sql 69 | .. automethod:: occurrences.download_describe 70 | .. automethod:: occurrences.download_citation 71 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_dataset_metrics.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/dataset/3f8a1297-3259-4700-91fc-acc4170b27ce/metrics 15 | response: 16 | body: 17 | string: '{"key":3171613,"datasetKey":"3f8a1297-3259-4700-91fc-acc4170b27ce","usagesCount":33312,"synonymsCount":22536,"distinctNamesCount":33282,"nubMatchingCount":31698,"colMatchingCount":28003,"nubCoveragePct":95,"colCoveragePct":84,"countByConstituent":{},"countByKingdom":{"PLANTAE":9369},"countByRank":{"SPECIES":6327,"GENUS":1284,"VARIETY":907,"SUBSPECIES":723,"SECTION":472,"TRIBE":332,"FAMILY":182,"SUBFAMILY":164,"SUBGENUS":140,"SUBTRIBE":69,"SUBSECTION":61,"ORDER":56,"SERIES":39,"SUPERORDER":13,"SUBCLASS":6,"CLASS":1},"countNamesByLanguage":{"ENGLISH":21095,"FRENCH":11592},"countExtRecordsByExtension":{"VERNACULAR_NAME":32687,"DISTRIBUTION":30141,"DESCRIPTION":10759,"IDENTIFIER":0,"MULTIMEDIA":0,"REFERENCE":0,"SPECIES_PROFILE":0,"TYPES_AND_SPECIMEN":0},"countByOrigin":{"SOURCE":33304,"PROPARTE":8},"countByIssue":{"SCIENTIFIC_NAME_ASSEMBLED":1951,"BACKBONE_MATCH_NONE":1614,"PARTIALLY_PARSABLE":44,"UNPARSABLE":3},"otherCount":{},"downloaded":"2024-08-29T19:02:34.364+00:00"}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '0' 23 | Cache-Control: 24 | - public, max-age=3601 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '983' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:13:30 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - '805307033' 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_backbone_checklistKey.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v2/species/match?scientificName=Calopteryx+splendens&checklistKey=7ddf754f-d193-4cc9-b351-99906754a03b 15 | response: 16 | body: 17 | string: '{"usage":{"key":"Q2M4","name":"Calopteryx splendens (Harris, 1780)","canonicalName":"Calopteryx 18 | splendens","authorship":"(Harris, 1780)","rank":"SPECIES","code":"ZOOLOGICAL","status":"ACCEPTED","genericName":"Calopteryx","specificEpithet":"splendens","type":"SCIENTIFIC","formattedName":"Calopteryx 19 | splendens (Harris, 1780)"},"classification":[{"key":"CS5HF","name":"Eukaryota","rank":"DOMAIN"},{"key":"N","name":"Animalia","rank":"KINGDOM"},{"key":"RT","name":"Arthropoda","rank":"PHYLUM"},{"key":"L2655","name":"Hexapoda","rank":"SUBPHYLUM"},{"key":"H6","name":"Insecta","rank":"CLASS"},{"key":"B6LCL","name":"Odonata","rank":"ORDER"},{"key":"B6NHX","name":"Zygoptera","rank":"SUBORDER"},{"key":"V5BP3","name":"Calopterygoidea","rank":"SUPERFAMILY"},{"key":"9WKK3","name":"Calopterygidae","rank":"FAMILY"},{"key":"9WLSS","name":"Calopteryx","rank":"GENUS"},{"key":"Q2M4","name":"Calopteryx 20 | splendens","rank":"SPECIES"}],"diagnostics":{"matchType":"EXACT","confidence":99,"timeTaken":13,"timings":{"nameNRank":0,"sciNameMatch":14,"nameParse":1,"luceneMatch":13}},"additionalStatus":[{"clbDatasetKey":"53131","datasetAlias":"IUCN","datasetKey":"19491596-35ae-4a91-9a98-85cf505f1bd3","status":"LEAST_CONCERN","statusCode":"LC","sourceId":"158717"}],"synonym":false}' 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '0' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Type: 31 | - application/json 32 | Date: 33 | - Fri, 14 Nov 2025 12:22:08 GMT 34 | Transfer-Encoding: 35 | - chunked 36 | Vary: 37 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 38 | Via: 39 | - 1.1 varnish (Varnish/6.6) 40 | X-Varnish: 41 | - '906363137' 42 | status: 43 | code: 200 44 | message: OK 45 | version: 1 46 | -------------------------------------------------------------------------------- /test/test-institution-search.py: -------------------------------------------------------------------------------- 1 | """tests for institution search""" 2 | import vcr 3 | from pygbif import institution 4 | 5 | keyz = ["count", "results", "endOfRecords", "limit", "offset"] 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_institution_search.yaml") 8 | def test_search(): 9 | "institution.search - basic test" 10 | res = institution.search(limit=10) 11 | assert "dict" == res.__class__.__name__ 12 | assert 5 == len(res) 13 | assert sorted(keyz) == sorted(res.keys()) 14 | assert 10 == len(res["results"]) 15 | assert res["count"] >= 8000 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_institution_search_q.yaml") 18 | def test_search_q(): 19 | "institution.search - q" 20 | res = institution.search(q="Kansas", limit=10) 21 | assert "dict" == res.__class__.__name__ 22 | assert 5 == len(res) 23 | assert sorted(keyz) == sorted(res.keys()) 24 | assert 10 == len(res["results"]) 25 | assert res["count"] >= 10 26 | assert res["count"] <= 8000 27 | 28 | @vcr.use_cassette("test/vcr_cassettes/test_institution_search_country.yaml") 29 | def test_search_country(): 30 | "institution.search - country" 31 | res = institution.search(country=["US", "GB"], limit=10) 32 | assert "dict" == res.__class__.__name__ 33 | assert 5 == len(res) 34 | assert sorted(keyz) == sorted(res.keys()) 35 | assert 10 == len(res["results"]) 36 | assert res["count"] >= 1000 37 | assert res["count"] <= 8000 38 | 39 | @vcr.use_cassette("test/vcr_cassettes/test_institution_search_active.yaml") 40 | def test_search_active(): 41 | "institution.search - active" 42 | res = institution.search(active=True, limit=10) 43 | assert "dict" == res.__class__.__name__ 44 | assert 5 == len(res) 45 | assert sorted(keyz) == sorted(res.keys()) 46 | assert 10 == len(res["results"]) 47 | for r in res["results"]: 48 | assert r["active"] is True 49 | 50 | @vcr.use_cassette("test/vcr_cassettes/test_institution_typeSpecimenCount.yaml") 51 | def test_search_typeSpecimenCount(): 52 | "institution.search - typeSpecimenCount" 53 | res = institution.search(typeSpecimenCount="10,100", limit=10) 54 | assert "dict" == res.__class__.__name__ 55 | assert 5 == len(res) 56 | assert sorted(keyz) == sorted(res.keys()) 57 | assert 10 == len(res["results"]) 58 | for r in res["results"]: 59 | assert r["typeSpecimenCount"] >= 10 60 | assert r["typeSpecimenCount"] <= 100 61 | 62 | -------------------------------------------------------------------------------- /test/test-collection-search.py: -------------------------------------------------------------------------------- 1 | """tests for collection search""" 2 | import vcr 3 | from pygbif import collection 4 | 5 | keyz = ["count", "results", "endOfRecords", "limit", "offset"] 6 | 7 | @vcr.use_cassette("test/vcr_cassettes/test_collection_search.yaml") 8 | def test_search(): 9 | "collection.search - basic test" 10 | res = collection.search(limit=10) 11 | assert "dict" == res.__class__.__name__ 12 | assert 5 == len(res) 13 | assert sorted(keyz) == sorted(res.keys()) 14 | assert 10 == len(res["results"]) 15 | assert res["count"] >= 10000 16 | 17 | @vcr.use_cassette("test/vcr_cassettes/test_collection_search_q.yaml") 18 | def test_search_q(): 19 | "collection.search - q" 20 | res = collection.search(q="Kansas", limit=10) 21 | assert "dict" == res.__class__.__name__ 22 | assert 5 == len(res) 23 | assert sorted(keyz) == sorted(res.keys()) 24 | assert 10 == len(res["results"]) 25 | assert res["count"] >= 30 26 | assert res["count"] <= 10000 27 | 28 | @vcr.use_cassette("test/vcr_cassettes/test_collection_search_country.yaml") 29 | def test_search_country(): 30 | "collection.search - country" 31 | res = collection.search(country=["US","GB"], limit=10) 32 | assert "dict" == res.__class__.__name__ 33 | assert 5 == len(res) 34 | assert sorted(keyz) == sorted(res.keys()) 35 | assert 10 == len(res["results"]) 36 | assert res["count"] >= 3000 37 | assert res["count"] <= 10000 38 | 39 | @vcr.use_cassette("test/vcr_cassettes/test_collection_search_active.yaml") 40 | def test_search_active(): 41 | "collection.search - active" 42 | res = collection.search(active=True, limit=10) 43 | assert "dict" == res.__class__.__name__ 44 | assert 5 == len(res) 45 | assert sorted(keyz) == sorted(res.keys()) 46 | assert 10 == len(res["results"]) 47 | assert res["count"] >= 9000 48 | for r in res["results"]: 49 | assert r["active"] is True 50 | 51 | @vcr.use_cassette("test/vcr_cassettes/test_collection_search_institutionKey.yaml") 52 | def test_search_institutionKey(): 53 | "collection.search - institutionKey" 54 | res = collection.search(institutionKey="6a6ac6c5-1b8a-48db-91a2-f8661274ff80", limit=10) 55 | assert "dict" == res.__class__.__name__ 56 | assert 5 == len(res) 57 | assert sorted(keyz) == sorted(res.keys()) 58 | assert 10 == len(res["results"]) 59 | assert res["count"] >= 20 60 | for r in res["results"]: 61 | assert r["institutionKey"] == "6a6ac6c5-1b8a-48db-91a2-f8661274ff80" 62 | 63 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_installations_uuid.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/installation/b77901f9-d9b0-47fa-94e0-dd96450aa2b4?limit=100 15 | response: 16 | body: 17 | string: '{"key":"b77901f9-d9b0-47fa-94e0-dd96450aa2b4","organizationKey":"8d30d714-63f5-4f6f-a1a4-d73d09aa60c4","type":"IPT_INSTALLATION","title":"IPT 18 | VIR N.I. Vavilov","description":"IPT\r\nN.I.Vavilov Institute of Plant Genetic 19 | Resources (VIR)","createdBy":"8d30d714-63f5-4f6f-a1a4-d73d09aa60c4","modifiedBy":"b77901f9-d9b0-47fa-94e0-dd96450aa2b4","created":"2013-12-05T12:28:06.094+00:00","modified":"2015-12-19T19:19:38.804+00:00","disabled":false,"contacts":[{"key":24655,"type":"TECHNICAL_POINT_OF_CONTACT","primary":true,"userId":[],"firstName":"Vladimir 20 | Korneev","position":[],"email":["ito@vir.nw.ru"],"phone":[],"homepage":[],"address":[],"createdBy":"8d30d714-63f5-4f6f-a1a4-d73d09aa60c4","modifiedBy":"b77901f9-d9b0-47fa-94e0-dd96450aa2b4","created":"2013-12-05T12:28:06.138+00:00","modified":"2015-12-19T19:19:38.874+00:00"}],"endpoints":[{"key":89965,"type":"FEED","url":"http://91.151.189.38:8080/viript/rss.do","createdBy":"b77901f9-d9b0-47fa-94e0-dd96450aa2b4","modifiedBy":"b77901f9-d9b0-47fa-94e0-dd96450aa2b4","created":"2015-12-19T19:19:39.011+00:00","modified":"2015-12-19T19:19:39.011+00:00","machineTags":[]}],"machineTags":[],"tags":[],"identifiers":[],"comments":[]}' 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '0' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Type: 31 | - application/json 32 | Date: 33 | - Fri, 14 Nov 2025 10:13:38 GMT 34 | Expires: 35 | - '0' 36 | Pragma: 37 | - no-cache 38 | Transfer-Encoding: 39 | - chunked 40 | Vary: 41 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 42 | Via: 43 | - 1.1 varnish (Varnish/6.6) 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Frame-Options: 47 | - DENY 48 | X-Varnish: 49 | - '790856529' 50 | X-XSS-Protection: 51 | - 1; mode=block 52 | status: 53 | code: 200 54 | message: OK 55 | version: 1 56 | -------------------------------------------------------------------------------- /test/test-maps-map.py: -------------------------------------------------------------------------------- 1 | """Tests for maps module - maps""" 2 | import pytest 3 | import unittest 4 | import vcr 5 | import requests 6 | import matplotlib 7 | 8 | matplotlib.use("Agg") 9 | import pygbif 10 | 11 | 12 | class TestMapsClass(unittest.TestCase): 13 | @vcr.use_cassette("test/vcr_cassettes/test_map.yaml") 14 | def test_map(self): 15 | "maps.map - basic test" 16 | res = pygbif.maps.map(taxonKey=2435098) 17 | self.assertIsInstance(res, pygbif.maps.GbifMap) 18 | self.assertIsInstance(res.response, requests.Response) 19 | self.assertIsInstance(res.path, str) 20 | self.assertIsInstance(res.img, matplotlib.image.AxesImage) 21 | 22 | def test_map_year_range(self): 23 | "maps.map - year range" 24 | res = pygbif.maps.map(taxonKey=2435098, year=range(2007, 2011 + 1)) 25 | self.assertIsInstance(res, pygbif.maps.GbifMap) 26 | self.assertRegex(res.response.request.path_url, "2007%2C2011") 27 | # self.assertIsInstance(res.path, str) 28 | # self.assertIsInstance(res.img, matplotlib.image.AxesImage) 29 | 30 | def test_map_basisofrecord_str_class(self): 31 | "maps.map - basisofrecord" 32 | res = pygbif.maps.map( 33 | taxonKey=2480498, year=2010, basisOfRecord="HUMAN_OBSERVATION" 34 | ) 35 | self.assertIsInstance(res, pygbif.maps.GbifMap) 36 | self.assertRegex(res.response.request.path_url, "basisOfRecord") 37 | self.assertRegex(res.response.request.path_url, "HUMAN_OBSERVATION") 38 | 39 | def test_map_basisofrecord_list_class(self): 40 | "maps.map - basisofrecord" 41 | res = pygbif.maps.map( 42 | taxonKey=2480498, 43 | year=2010, 44 | basisOfRecord=["HUMAN_OBSERVATION", "LIVING_SPECIMEN"], 45 | ) 46 | self.assertIsInstance(res, pygbif.maps.GbifMap) 47 | self.assertRegex(res.response.request.path_url, "basisOfRecord") 48 | self.assertRegex(res.response.request.path_url, "HUMAN_OBSERVATION") 49 | self.assertRegex(res.response.request.path_url, "LIVING_SPECIMEN") 50 | 51 | def test_maps_fails_well(self): 52 | "maps.map - fails well" 53 | with pytest.raises(ValueError): 54 | pygbif.maps.map(year=2300) 55 | pygbif.maps.map(year="2010") 56 | pygbif.maps.map(basisOfRecord="foobar") 57 | pygbif.maps.map(format="foobar") 58 | pygbif.maps.map(source="foobar") 59 | pygbif.maps.map(srs="foobar") 60 | pygbif.maps.map(bin="foobar") 61 | pygbif.maps.map(style="foobar") 62 | -------------------------------------------------------------------------------- /pygbif/registry/networks.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import ( 2 | check_data, 3 | stop, 4 | gbif_baseurl, 5 | gbif_GET, 6 | get_meta, 7 | parse_results, 8 | len2, 9 | ) 10 | 11 | 12 | def networks( 13 | data="all", 14 | uuid=None, 15 | q=None, 16 | identifier=None, 17 | identifierType=None, 18 | limit=100, 19 | offset=None, 20 | **kwargs 21 | ): 22 | """ 23 | Networks metadata. 24 | 25 | Note: there's only 1 network now, so there's not a lot you can do with this method. 26 | 27 | :param data: [str] The type of data to get. Default: ``all`` 28 | :param uuid: [str] UUID of the data network provider. This must be specified if data 29 | is anything other than ``all``. 30 | :param q: [str] Query networks. Only used when ``data = 'all'``. Ignored otherwise. 31 | :param identifier: [fixnum] The value for this parameter can be a simple string or integer, 32 | e.g. identifier=120 33 | :param identifierType: [str] Used in combination with the identifier parameter to filter 34 | identifiers by identifier type: ``DOI``, ``FTP``, ``GBIF_NODE``, ``GBIF_PARTICIPANT``, 35 | ``GBIF_PORTAL``, ``HANDLER``, ``LSID``, ``UNKNOWN``, ``URI``, ``URL``, ``UUID`` 36 | :param limit: [int] Number of results to return. Default: ``100`` 37 | :param offset: [int] Record to start at. Default: ``0`` 38 | 39 | :return: A dictionary 40 | 41 | References: http://www.gbif.org/developer/registry#networks 42 | 43 | Usage:: 44 | 45 | from pygbif import registry 46 | registry.networks(limit=1) 47 | registry.networks(uuid='2b7c7b4f-4d4f-40d3-94de-c28b6fa054a6') 48 | """ 49 | args = { 50 | "q": q, 51 | "limit": limit, 52 | "offset": offset, 53 | "identifier": identifier, 54 | "identifierType": identifierType, 55 | } 56 | data_choices = [ 57 | "all", 58 | "contact", 59 | "endpoint", 60 | "identifier", 61 | "tag", 62 | "machineTag", 63 | "comment", 64 | "constituents", 65 | ] 66 | check_data(data, data_choices) 67 | 68 | def getdata(x, uuid, args, **kwargs): 69 | if x != "all" and uuid is None: 70 | stop('You must specify a uuid if data does not equal "all"') 71 | 72 | if uuid is None: 73 | url = gbif_baseurl + "network" 74 | else: 75 | if x == "all": 76 | url = gbif_baseurl + "network/" + uuid 77 | else: 78 | url = gbif_baseurl + "network/" + uuid + "/" + x 79 | 80 | res = gbif_GET(url, args, **kwargs) 81 | return {"meta": get_meta(res), "data": parse_results(res, uuid)} 82 | 83 | if len2(data) == 1: 84 | return getdata(data, uuid, args, **kwargs) 85 | else: 86 | return [getdata(x, uuid, args, **kwargs) for x in data] 87 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_get_verbatim.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/142316497/verbatim 15 | response: 16 | body: 17 | string: "{\"key\":142316497,\"datasetKey\":\"8575f23e-f762-11e1-a439-00145eb45e9a\",\"publishingOrgKey\":\"57254bd0-8256-11d8-b7ed-b8a03c50a862\",\"networkKeys\":[\"17abcf75-2f1e-46dd-bf75-a5b21dd02655\"],\"installationKey\":\"60454014-f762-11e1-a439-00145eb45e9a\",\"publishingCountry\":\"DE\",\"protocol\":\"BIOCASE\",\"lastCrawled\":\"2025-11-01T22:19:55.624+00:00\",\"lastParsed\":\"2025-11-05T04:17:51.395+00:00\",\"crawlId\":306,\"extensions\":{},\"publishedByGbifRegion\":\"EUROPE\",\"http://rs.gbif.org/terms/1.0/gbifID\":\"142316497\",\"http://rs.tdwg.org/dwc/terms/kingdom\":\"Plantae\",\"http://purl.org/dc/terms/identifier\":\"urn:catalog:BGBM:Pontaurus:1\",\"http://rs.tdwg.org/dwc/terms/class\":\"Magnoliopsida\",\"http://rs.tdwg.org/dwc/terms/country\":\"Turkey\",\"http://rs.tdwg.org/dwc/terms/collectionCode\":\"Pontaurus\",\"http://rs.tdwg.org/dwc/terms/order\":\"Lamiales\",\"http://rs.tdwg.org/dwc/terms/minimumElevationInMeters\":\"0\",\"http://rs.tdwg.org/dwc/terms/identifiedBy\":\"Markus 18 | D\xF6ring\",\"http://rs.tdwg.org/dwc/terms/basisOfRecord\":\"specimen\",\"http://rs.tdwg.org/dwc/terms/eventDate\":\"1999-06-18T00:00:00\",\"http://rs.tdwg.org/dwc/terms/catalogNumber\":\"1\",\"http://rs.tdwg.org/dwc/terms/family\":\"Lamiaceae\",\"http://rs.tdwg.org/dwc/terms/decimalLatitude\":\"38.10695\",\"http://rs.tdwg.org/dwc/terms/decimalLongitude\":\"26.85083\",\"http://rs.tdwg.org/dwc/terms/institutionCode\":\"BGBM\",\"http://rs.tdwg.org/dwc/terms/scientificName\":\"Ballota 19 | acetabulosa (L.) Benth.\",\"http://rs.tdwg.org/dwc/terms/recordedBy\":\"Markus 20 | D\xF6ring\",\"http://rs.tdwg.org/dwc/terms/phylum\":\"Magnoliophyta\"}" 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '660' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Length: 31 | - '1517' 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Fri, 14 Nov 2025 10:02:24 GMT 36 | Expires: 37 | - '0' 38 | Pragma: 39 | - no-cache 40 | Vary: 41 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 42 | Via: 43 | - 1.1 varnish (Varnish/6.6) 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Frame-Options: 47 | - DENY 48 | X-Varnish: 49 | - 799900748 697272188 50 | X-XSS-Protection: 51 | - '0' 52 | status: 53 | code: 200 54 | message: OK 55 | version: 1 56 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_suggest_paging.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/species/suggest?q=Aso&limit=3 15 | response: 16 | body: 17 | string: '[{"key":5429462,"nameKey":1046999,"kingdom":"Animalia","phylum":"Mollusca","order":"Architaenioglossa","family":"Ampullariidae","genus":"Asolene","kingdomKey":1,"phylumKey":52,"classKey":225,"orderKey":455,"familyKey":6801,"genusKey":5429462,"parent":"Ampullariidae","parentKey":6801,"nubKey":5429462,"scientificName":"Asolene 18 | d''Orbigny, 1838","canonicalName":"Asolene","rank":"GENUS","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"1":"Animalia","52":"Mollusca","225":"Gastropoda","455":"Architaenioglossa","6801":"Ampullariidae"},"class":"Gastropoda"},{"key":1459644,"nameKey":1047180,"kingdom":"Animalia","phylum":"Arthropoda","order":"Diptera","family":"Acroceridae","genus":"Asopsebius","kingdomKey":1,"phylumKey":54,"classKey":216,"orderKey":811,"familyKey":3328,"genusKey":1459644,"parent":"Acroceridae","parentKey":3328,"nubKey":1459644,"scientificName":"Asopsebius 19 | Nartshuk, 1982","canonicalName":"Asopsebius","rank":"GENUS","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"1":"Animalia","54":"Arthropoda","216":"Insecta","811":"Diptera","3328":"Acroceridae"},"class":"Insecta"},{"key":4859837,"nameKey":1047028,"kingdom":"Animalia","phylum":"Platyhelminthes","family":"Callioplanidae","genus":"Asolenia","kingdomKey":1,"phylumKey":108,"classKey":341,"familyKey":6376,"genusKey":4859837,"parent":"Callioplanidae","parentKey":6376,"nubKey":4859837,"scientificName":"Asolenia 20 | Hyman, 1959","canonicalName":"Asolenia","rank":"GENUS","status":"ACCEPTED","synonym":false,"higherClassificationMap":{"1":"Animalia","108":"Platyhelminthes","341":"Turbellaria","6376":"Callioplanidae"},"class":"Turbellaria"}]' 21 | headers: 22 | Accept-Ranges: 23 | - bytes 24 | Age: 25 | - '1392' 26 | Cache-Control: 27 | - public, max-age=3601 28 | Connection: 29 | - keep-alive 30 | Content-Length: 31 | - '1639' 32 | Content-Type: 33 | - application/json 34 | Date: 35 | - Fri, 14 Nov 2025 10:03:00 GMT 36 | Expires: 37 | - '0' 38 | Pragma: 39 | - no-cache 40 | Vary: 41 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 42 | Via: 43 | - 1.1 varnish (Varnish/6.6) 44 | X-Content-Type-Options: 45 | - nosniff 46 | X-Frame-Options: 47 | - DENY 48 | X-Varnish: 49 | - 732432314 732397781 50 | X-XSS-Protection: 51 | - 1; mode=block 52 | status: 53 | code: 200 54 | message: OK 55 | version: 1 56 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_get_fragment.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/1986620884/fragment 15 | response: 16 | body: 17 | string: '{"basisOfRecord":"HumanObservation","captive":"wild","catalogNumber":"19535428","class":"Mammalia","collectionCode":"Observations","coordinateUncertaintyInMeters":"19","countryCode":"UA","datasetName":"iNaturalist 18 | research-grade observations","dateIdentified":"2019-01-09T18:12:11Z","decimalLatitude":"48.7551693647","decimalLongitude":"25.1021547229","eventDate":"2019-01-09T18:06:54+02:00","eventTime":"18:06:54+02:00","extensions":{"gbif:Multimedia":[{"catalogNumber":"30065447","created":"2019-01-09T16:06:54Z","creator":"John 19 | Waller","format":"image/jpeg","identifier":"https://inaturalist-open-data.s3.amazonaws.com/photos/30065447/original.jpg","license":"http://creativecommons.org/licenses/by-nc/4.0/","publisher":"iNaturalist","references":"https://www.inaturalist.org/photos/30065447","rightsHolder":"John 20 | Waller","type":"StillImage"}]},"family":"Echimyidae","genus":"Myocastor","geodeticDatum":"EPSG:4326","id":"19535428","identificationID":"42848020","identifiedBy":"John 21 | Waller","institutionCode":"iNaturalist","kingdom":"Animalia","license":"http://creativecommons.org/publicdomain/zero/1.0/","lifeStage":"adult","modified":"2023-02-10T01:24:27Z","nick":"pigeonspotters1888","occurrenceID":"https://www.inaturalist.org/observations/19535428","order":"Rodentia","phylum":"Chordata","recordedBy":"John 22 | Waller","references":"https://www.inaturalist.org/observations/19535428","rightsHolder":"John 23 | Waller","scientificName":"Myocastor coypus","stateProvince":"Ivano-Frankivs''k","taxonID":"43997","taxonRank":"species","verbatimEventDate":"Wed 24 | Jan 09 2019 20:06:54 GMT+0200 (GMT+2)","verbatimLocality":"Khotymyr, Khotymyr, 25 | Ivano-Frankivsk Oblast, UA"}' 26 | headers: 27 | Accept-Ranges: 28 | - bytes 29 | Age: 30 | - '660' 31 | Cache-Control: 32 | - public, max-age=3601 33 | Connection: 34 | - keep-alive 35 | Content-Length: 36 | - '1660' 37 | Content-Type: 38 | - application/json 39 | Date: 40 | - Fri, 14 Nov 2025 10:02:25 GMT 41 | Expires: 42 | - '0' 43 | Pragma: 44 | - no-cache 45 | Vary: 46 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 47 | Via: 48 | - 1.1 varnish (Varnish/6.6) 49 | X-Content-Type-Options: 50 | - nosniff 51 | X-Frame-Options: 52 | - DENY 53 | X-Varnish: 54 | - 704973264 713196963 55 | X-XSS-Protection: 56 | - '0' 57 | status: 58 | code: 200 59 | message: OK 60 | version: 1 61 | -------------------------------------------------------------------------------- /test/test-occurrences-count.py: -------------------------------------------------------------------------------- 1 | """Tests for occurrences module - count methods""" 2 | import pytest 3 | import vcr 4 | from pygbif import occurrences 5 | 6 | brecord_res = [ 7 | "HUMAN_OBSERVATION", 8 | "PRESERVED_SPECIMEN", 9 | "MATERIAL_SAMPLE", 10 | "OBSERVATION", 11 | "MACHINE_OBSERVATION", 12 | "OCCURRENCE", 13 | "FOSSIL_SPECIMEN", 14 | "MATERIAL_CITATION", 15 | "LIVING_SPECIMEN" 16 | ] 17 | 18 | year_res = [ 19 | "1991", 20 | "1990", 21 | "1993", 22 | "1992", 23 | "1995", 24 | "1994", 25 | "1997", 26 | "1996", 27 | "1999", 28 | "1998", 29 | "2000", 30 | ] 31 | 32 | 33 | @vcr.use_cassette("test/vcr_cassettes/test_count.yaml") 34 | def test_count(): 35 | "occurrences.count - basic test" 36 | res = occurrences.count(taxonKey=3329049) 37 | assert int == res.__class__ 38 | 39 | 40 | def test_count_param_length(): 41 | "occurrences.count_param_length" 42 | with pytest.raises(TypeError): 43 | occurrences.count(datasetKey=["foo", "bar"]) 44 | 45 | 46 | @vcr.use_cassette("test/vcr_cassettes/test_count_basisofrecord.yaml") 47 | def test_count_basisofrecord(): 48 | "occurrences.count_basisofrecord - basic test" 49 | res = occurrences.count_basisofrecord() 50 | assert dict == res.__class__ 51 | assert 9 == len(res) 52 | assert sorted(brecord_res) == sorted(res.keys()) 53 | 54 | 55 | @vcr.use_cassette("test/vcr_cassettes/test_count_year.yaml") 56 | def test_count_year(): 57 | "occurrences.count_year - basic test" 58 | res = occurrences.count_year(year="1990,2000") 59 | assert dict == res.__class__ 60 | assert 11 == len(res) 61 | assert sorted(year_res) == sorted(res.keys()) 62 | 63 | 64 | @vcr.use_cassette("test/vcr_cassettes/test_count_datasets.yaml") 65 | def test_count_datasets(): 66 | "occurrences.count_datasets - basic test" 67 | res = occurrences.count_datasets(country="DE") 68 | assert dict == res.__class__ 69 | assert str == str(list(res.keys())[0]).__class__ 70 | 71 | 72 | @vcr.use_cassette("test/vcr_cassettes/test_count_countries.yaml") 73 | def test_count_countries(): 74 | "occurrences.count_countries - basic test" 75 | res = occurrences.count_countries(publishingCountry="DE") 76 | assert dict == res.__class__ 77 | assert str == str(list(res.keys())[0]).__class__ 78 | assert int == list(res.values())[0].__class__ 79 | 80 | 81 | @vcr.use_cassette("test/vcr_cassettes/test_count_schema.yaml") 82 | def test_count_schema(): 83 | "occurrences.count_schema - basic test" 84 | res = occurrences.count_schema() 85 | assert list == res.__class__ 86 | assert dict == res[0].__class__ 87 | assert "dimensions" == list(res[0].keys())[0] 88 | 89 | 90 | @vcr.use_cassette("test/vcr_cassettes/test_count_publishingcountries.yaml") 91 | def test_count_publishingcountries(): 92 | "occurrences.count_publishingcountries - basic test" 93 | res = occurrences.count_publishingcountries(country="DE") 94 | assert dict == res.__class__ 95 | assert str == str(list(res.keys())[0]).__class__ 96 | assert int == list(res.values())[0].__class__ 97 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_literature_search_key.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/literature/search?gbifDownloadKey=0235283-220831081235567&limit=100&offset=0 15 | response: 16 | body: 17 | string: '{"offset":0,"limit":100,"endOfRecords":true,"count":1,"results":[{"discovered":"2024-09-24","authors":[{"firstName":"John","lastName":"Waller"}],"countriesOfCoverage":[],"countriesOfResearcher":["DK"],"publishingCountry":["EC","NO","NL","CH","PL","SR","SE","ZZ","FI","ES","DK","CO","BE","BR","UA","GB","DE","EE","US","FR","RU","AT","IE","CA"],"added":"2024-09-24T13:25:01.323+00:00","published":"2024-09-23T00:00:00.000+00:00","day":23,"gbifDownloadKey":["0235283-220831081235567","0026791-240906103802322"],"gbifOccurrenceKey":[],"gbifTaxonKey":[],"gbifHigherTaxonKey":[],"gbifNetworkKey":["99d66b6c-9087-452f-a9d4-f15f2c2d0e7e","379a0de5-f377-4661-9a30-33dd844e7b9a","17abcf75-2f1e-46dd-bf75-a5b21dd02655","0b00b924-016b-4954-96a7-2d9264b5d0ba","1f2c0cbe-40df-43f6-ba07-e76133e78c31","8e30b684-6a54-4aba-aa73-66201c2c736e","2ee1bff7-0b34-4fa3-9433-feaa7c6ee08b","d1627240-04ab-4162-aee9-b16df6bc8308","2b7c7b4f-4d4f-40d3-94de-c28b6fa054a6"],"gbifProjectIdentifier":["SBP-BioBlitz 18 | 2017","BID-CA2016-0006-REG","IMBIO","CESP2022-010","meetnetten.be","34873-1"],"gbifProgramme":["CESP"],"citationType":"DOI","gbifRegion":[],"id":"89c37dab-cce3-34ee-a03e-4d6d9940c68b","identifiers":{},"keywords":[],"language":"eng","literatureType":"JOURNAL","month":9,"notes":"No 19 | specific funding information provided.","openAccess":false,"peerReview":false,"publisher":"GBIF 20 | Secretariat","relevance":["GBIF_USED"],"tags":["2024","Biodiversity_science","DK","GBIF_used","citation_scope:ok","citation_type:DOI","gbifDOI:10.15468/dl.29wbtx","gbifDOI:10.15468/dl.jg2m96","lit_source:user","open_access:FALSE","peer_review:FALSE"],"title":"(used 21 | for rgbif testing)","topics":["BIODIVERSITY_SCIENCE"],"modified":"2024-09-24T13:25:01.323+00:00","websites":[],"year":2024,"abstract":"(no 22 | abstract available)"}],"facets":[]}' 23 | headers: 24 | Accept-Ranges: 25 | - bytes 26 | Age: 27 | - '0' 28 | Cache-Control: 29 | - public, max-age=600 30 | Connection: 31 | - keep-alive 32 | Content-Length: 33 | - '1799' 34 | Content-Type: 35 | - application/json 36 | Date: 37 | - Fri, 14 Nov 2025 10:13:12 GMT 38 | Expires: 39 | - '0' 40 | Pragma: 41 | - no-cache 42 | Vary: 43 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 44 | Via: 45 | - 1.1 varnish (Varnish/6.6) 46 | X-Content-Type-Options: 47 | - nosniff 48 | X-Frame-Options: 49 | - DENY 50 | X-Varnish: 51 | - '679479750' 52 | X-XSS-Protection: 53 | - 1; mode=block 54 | status: 55 | code: 200 56 | message: OK 57 | version: 1 58 | -------------------------------------------------------------------------------- /pygbif/species/name_suggest.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import gbif_baseurl, gbif_GET 2 | 3 | 4 | def name_suggest(q=None, datasetKey=None, rank=None, limit=100, offset=None, **kwargs): 5 | """ 6 | A quick and simple autocomplete service that returns up to 20 name usages by 7 | doing prefix matching against the scientific name. Results are ordered by relevance. 8 | 9 | :param q: [str] Simple search parameter. The value for this parameter can be a 10 | simple word or a phrase. Wildcards can be added to the simple word parameters only, 11 | e.g. ``q=*puma*`` (Required) 12 | :param datasetKey: [str] Filters by the checklist dataset key (a uuid, see examples) 13 | :param rank: [str] A taxonomic rank. One of ``class``, ``cultivar``, ``cultivar_group``, ``domain``, ``family``, 14 | ``form``, ``genus``, ``informal``, ``infrageneric_name``, ``infraorder``, ``infraspecific_name``, 15 | ``infrasubspecific_name``, ``kingdom``, ``order``, ``phylum``, ``section``, ``series``, ``species``, ``strain``, ``subclass``, 16 | ``subfamily``, ``subform``, ``subgenus``, ``subkingdom``, ``suborder``, ``subphylum``, ``subsection``, ``subseries``, 17 | ``subspecies``, ``subtribe``, ``subvariety``, ``superclass``, ``superfamily``, ``superorder``, ``superphylum``, 18 | ``suprageneric_name``, ``tribe``, ``unranked``, or ``variety``. 19 | :param limit: [fixnum] Number of records to return. Maximum: ``1000``. (optional) 20 | :param offset: [fixnum] Record number to start at. (optional) 21 | 22 | :return: A dictionary 23 | 24 | References: http://www.gbif.org/developer/species#searching 25 | 26 | Usage:: 27 | 28 | from pygbif import species 29 | 30 | species.name_suggest(q='Puma concolor') 31 | x = species.name_suggest(q='Puma') 32 | species.name_suggest(q='Puma', rank="genus") 33 | species.name_suggest(q='Puma', rank="subspecies") 34 | species.name_suggest(q='Puma', rank="species") 35 | species.name_suggest(q='Puma', rank="infraspecific_name") 36 | species.name_suggest(q='Puma', limit=2) 37 | """ 38 | url = gbif_baseurl + "species/suggest" 39 | args = {"q": q, "rank": rank, "offset": offset, "limit": limit} 40 | return gbif_GET(url, args, **kwargs) 41 | 42 | 43 | def suggestfields(): 44 | """ 45 | Fields available in ``gbif_suggest()`` function 46 | """ 47 | return [ 48 | "key", 49 | "datasetTitle", 50 | "datasetKey", 51 | "nubKey", 52 | "parentKey", 53 | "parent", 54 | "kingdom", 55 | "phylum", 56 | "class", 57 | "order", 58 | "family", 59 | "genus", 60 | "species", 61 | "kingdomKey", 62 | "phylumKey", 63 | "classKey", 64 | "orderKey", 65 | "familyKey", 66 | "genusKey", 67 | "speciesKey", 68 | "species", 69 | "canonicalName", 70 | "authorship", 71 | "accordingTo", 72 | "nameType", 73 | "taxonomicStatus", 74 | "rank", 75 | "numDescendants", 76 | "numOccurrences", 77 | "sourceId", 78 | "nomenclaturalStatus", 79 | "threatStatuses", 80 | "synonym", 81 | "higherClassificationMap", 82 | ] 83 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_organizations_uuid.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/organization/e2e717bf-551a-4917-bdc9-4fa0f342c530?limit=100 15 | response: 16 | body: 17 | string: '{"key":"e2e717bf-551a-4917-bdc9-4fa0f342c530","endorsingNodeKey":"8618c64a-93e0-4300-b546-7249e5148ed2","endorsementApproved":true,"endorsementStatus":"ENDORSED","title":"Cornell 18 | Lab of Ornithology","description":"The Cornell Lab of Ornithology is a world 19 | leader in the study, appreciation, and conservation of birds.","language":"eng","email":[],"phone":[],"homepage":["http://www.birds.cornell.edu"],"address":["159 20 | Sapsucker Woods Rd."],"city":"Ithaca","province":"New York","country":"US","postalCode":"14850","latitude":42.47999,"longitude":-76.45109,"numPublishedDatasets":3,"createdBy":"ADMIN","modifiedBy":"sformel","created":"2013-08-01T08:38:21.000+00:00","modified":"2023-03-03T01:57:24.373+00:00","contacts":[{"key":5261061,"type":"ADMINISTRATIVE_POINT_OF_CONTACT","primary":true,"userId":[],"firstName":"Chris","lastName":"Wood","position":["Program 21 | Director"],"email":["clw37@cornell.edu"],"phone":[],"homepage":[],"organization":"Cornell 22 | Lab of Ornithology","address":["Center for Avian Population Studies"],"createdBy":"mgrosjean","modifiedBy":"trobertson","created":"2024-03-25T07:41:21.878+00:00","modified":"2025-08-11T06:47:43.725+00:00"},{"key":6203930,"type":"TECHNICAL_POINT_OF_CONTACT","primary":true,"userId":[],"firstName":"Jasdev","lastName":"Imani","position":["Technical 23 | Lead"],"email":["jasdev.imani@cornell.edu"],"phone":[],"homepage":[],"organization":"Cornell 24 | Lab of Ornithology","address":["Center for Avian Population Studies"],"createdBy":"trobertson","modifiedBy":"trobertson","created":"2025-08-11T06:46:56.128+00:00","modified":"2025-08-11T06:46:56.128+00:00"}],"endpoints":[],"machineTags":[],"tags":[],"identifiers":[{"key":13528,"type":"GBIF_PORTAL","identifier":"602","createdBy":"registry-migration.gbif.org","created":"2013-08-08T09:34:37.000+00:00","primary":false}],"comments":[{"key":35635,"content":"2023-03-25 25 | [MG] Updated the contacts at the request of the institution.","createdBy":"mgrosjean","modifiedBy":"mgrosjean","created":"2024-03-25T07:39:32.978+00:00","modified":"2024-03-25T07:39:32.978+00:00"},{"key":33423,"content":"2023-03-02 26 | [SF] added address information","createdBy":"sformel","modifiedBy":"sformel","created":"2023-03-03T01:57:24.682+00:00","modified":"2023-03-03T01:57:24.682+00:00"}]}' 27 | headers: 28 | Accept-Ranges: 29 | - bytes 30 | Age: 31 | - '0' 32 | Cache-Control: 33 | - public, max-age=3601 34 | Connection: 35 | - keep-alive 36 | Content-Length: 37 | - '2256' 38 | Content-Type: 39 | - application/json 40 | Date: 41 | - Fri, 14 Nov 2025 10:13:48 GMT 42 | Expires: 43 | - '0' 44 | Pragma: 45 | - no-cache 46 | Vary: 47 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 48 | Via: 49 | - 1.1 varnish (Varnish/6.6) 50 | X-Content-Type-Options: 51 | - nosniff 52 | X-Frame-Options: 53 | - DENY 54 | X-Varnish: 55 | - '802653127' 56 | X-XSS-Protection: 57 | - 1; mode=block 58 | status: 59 | code: 200 60 | message: OK 61 | version: 1 62 | -------------------------------------------------------------------------------- /pygbif/caching.py: -------------------------------------------------------------------------------- 1 | import requests_cache 2 | import os.path 3 | import tempfile 4 | 5 | def caching( 6 | cache=False, 7 | name=None, 8 | backend="sqlite", 9 | expire_after=86400, 10 | allowable_codes=(200,), 11 | allowable_methods=("GET",), 12 | ): 13 | """ 14 | pygbif caching management 15 | 16 | :param cache: [bool] if ``True`` all http requests are cached. if ``False`` (default), 17 | no http requests are cached. 18 | :param name: [str] the cache name. when backend=sqlite, this is the path for the 19 | sqlite file, ignored if sqlite not used. if not set, the file is put in your 20 | temporary directory, and therefore is cleaned up/deleted after closing your 21 | python session 22 | :param backend: [str] the backend, one of: 23 | 24 | - ``sqlite`` sqlite database (default) 25 | - ``memory`` not persistent, stores all data in Python dict in memory 26 | - ``mongodb`` (experimental) MongoDB database (pymongo < 3.0 required and configured) 27 | - ``redis`` stores all data on a redis data store (redis required and configured) 28 | 29 | :param expire_after: [str] timedelta or number of seconds after cache will be expired 30 | or None (default) to ignore expiration. default: 86400 seconds (24 hrs) 31 | :param allowable_codes: [tuple] limit caching only for response with this codes 32 | (default: 200) 33 | :param allowable_methods: [tuple] cache only requests of this methods 34 | (default: ‘GET’) 35 | 36 | :return: sets options to be used by pygbif, returns the options you selected 37 | in a hash 38 | 39 | Note: setting cache=False will turn off caching, but the backend data still 40 | persists. thus, you can turn caching back on without losing your cache. 41 | this also means if you want to delete your cache you have to do it yourself. 42 | 43 | Note: on loading pygbif, we clean up expired responses 44 | 45 | Usage:: 46 | 47 | import pygbif 48 | 49 | # caching is off by default 50 | from pygbif import occurrences 51 | %time z=occurrences.search(taxonKey = 3329049) 52 | %time w=occurrences.search(taxonKey = 3329049) 53 | 54 | # turn caching on 55 | pygbif.caching(True) 56 | 57 | %time z=occurrences.search(taxonKey = 3329049) 58 | %time w=occurrences.search(taxonKey = 3329049) 59 | 60 | # set a different backend 61 | pygbif.caching(cache=True, backend="redis") 62 | %time z=occurrences.search(taxonKey = 3329049) 63 | %time w=occurrences.search(taxonKey = 3329049) 64 | 65 | # set a different backend 66 | pygbif.caching(cache=True, backend="mongodb") 67 | %time z=occurrences.search(taxonKey = 3329049) 68 | %time w=occurrences.search(taxonKey = 3329049) 69 | 70 | # set path to a sqlite file 71 | pygbif.caching(name = "some/path/my_file") 72 | """ 73 | default_name = "pygbif_requests_cache" 74 | if not cache: 75 | requests_cache.uninstall_cache() 76 | CACHE_NAME = None 77 | else: 78 | if name is None and backend == "sqlite": 79 | CACHE_NAME = os.path.join(tempfile.gettempdir(), default_name) 80 | else: 81 | CACHE_NAME = default_name 82 | 83 | requests_cache.install_cache( 84 | cache_name=CACHE_NAME, backend=backend, expire_after=expire_after 85 | ) 86 | requests_cache.delete(expired=True) 87 | 88 | cache_settings = { 89 | "cache": cache, 90 | "name": CACHE_NAME, 91 | "backend": backend, 92 | "expire_after": expire_after, 93 | "allowable_codes": allowable_codes, 94 | "allowable_methods": allowable_methods, 95 | } 96 | return cache_settings 97 | -------------------------------------------------------------------------------- /pygbif/registry/organizations.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import ( 2 | check_data, 3 | stop, 4 | gbif_baseurl, 5 | gbif_GET, 6 | get_meta, 7 | parse_results, 8 | len2, 9 | ) 10 | 11 | 12 | def organizations( 13 | data="all", 14 | uuid=None, 15 | q=None, 16 | identifier=None, 17 | identifierType=None, 18 | limit=100, 19 | offset=None, 20 | **kwargs 21 | ): 22 | """ 23 | Organizations metadata. 24 | 25 | :param data: [str] The type of data to get. Default is all data. If not ``all``, then one 26 | or more of ``contact``, ``endpoint``, ``identifier``, ``tag``, ``machineTag``, 27 | ``comment``, ``hostedDataset``, ``ownedDataset``, ``deleted``, ``pending``, 28 | ``nonPublishing``. 29 | :param uuid: [str] UUID of the data node provider. This must be specified if data 30 | is anything other than ``all``. 31 | :param q: [str] Query nodes. Only used when ``data='all'``. Ignored otherwise. 32 | :param identifier: [fixnum] The value for this parameter can be a simple string or integer, 33 | e.g. identifier=120 34 | :param identifierType: [str] Used in combination with the identifier parameter to filter 35 | identifiers by identifier type: ``DOI``, ``FTP``, ``GBIF_NODE``, ``GBIF_PARTICIPANT``, 36 | ``GBIF_PORTAL``, ``HANDLER``, ``LSID``, ``UNKNOWN``, ``URI``, ``URL``, ``UUID`` 37 | :param limit: [int] Number of results to return. Default: ``100`` 38 | :param offset: [int] Record to start at. Default: ``0`` 39 | 40 | :return: A dictionary 41 | 42 | References: http://www.gbif.org/developer/registry#organizations 43 | 44 | Usage:: 45 | 46 | from pygbif import registry 47 | registry.organizations(limit=5) 48 | registry.organizations(q="france") 49 | registry.organizations(identifier=120) 50 | registry.organizations(uuid="e2e717bf-551a-4917-bdc9-4fa0f342c530") 51 | registry.organizations(data='contact', uuid="e2e717bf-551a-4917-bdc9-4fa0f342c530") 52 | registry.organizations(data='deleted') 53 | registry.organizations(data='deleted', limit=2) 54 | registry.organizations(data=['deleted','nonPublishing'], limit=2) 55 | registry.organizations(identifierType='DOI', limit=2) 56 | """ 57 | args = { 58 | "q": q, 59 | "limit": limit, 60 | "offset": offset, 61 | "identifier": identifier, 62 | "identifierType": identifierType, 63 | } 64 | data_choices = [ 65 | "all", 66 | "contact", 67 | "endpoint", 68 | "identifier", 69 | "tag", 70 | "machineTag", 71 | "comment", 72 | "hostedDataset", 73 | "ownedDataset", 74 | "deleted", 75 | "pending", 76 | "nonPublishing", 77 | ] 78 | check_data(data, data_choices) 79 | 80 | def getdata(x, uuid, args, **kwargs): 81 | nouuid = ["all", "deleted", "pending", "nonPublishing"] 82 | if x not in nouuid and uuid is None: 83 | stop( 84 | 'You must specify a uuid if data does not equal "all" and data does not equal one of ' 85 | + ", ".join(nouuid) 86 | ) 87 | 88 | if uuid is None: 89 | if x == "all": 90 | url = gbif_baseurl + "organization" 91 | else: 92 | url = gbif_baseurl + "organization/" + x 93 | else: 94 | if x == "all": 95 | url = gbif_baseurl + "organization/" + uuid 96 | else: 97 | url = gbif_baseurl + "organization/" + uuid + "/" + x 98 | 99 | res = gbif_GET(url, args, **kwargs) 100 | return {"meta": get_meta(res), "data": parse_results(res, uuid)} 101 | 102 | if len2(data) == 1: 103 | return getdata(data, uuid, args, **kwargs) 104 | else: 105 | return [getdata(x, uuid, args, **kwargs) for x in data] 106 | -------------------------------------------------------------------------------- /pygbif/registry/installations.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import ( 2 | check_data, 3 | stop, 4 | gbif_baseurl, 5 | gbif_GET, 6 | get_meta, 7 | parse_results, 8 | len2, 9 | ) 10 | 11 | 12 | def installations( 13 | data="all", 14 | uuid=None, 15 | q=None, 16 | identifier=None, 17 | identifierType=None, 18 | limit=100, 19 | offset=None, 20 | **kwargs 21 | ): 22 | """ 23 | Installations metadata. 24 | 25 | :param data: [str] The type of data to get. Default is all data. If not ``all``, then one 26 | or more of ``contact``, ``endpoint``, ``dataset``, ``comment``, ``deleted``, ``nonPublishing``. 27 | :param uuid: [str] UUID of the data node provider. This must be specified if data 28 | is anything other than ``all``. 29 | :param q: [str] Query nodes. Only used when ``data='all'``. Ignored otherwise. 30 | :param identifier: [fixnum] The value for this parameter can be a simple string or integer, 31 | e.g. identifier=120 32 | :param identifierType: [str] Used in combination with the identifier parameter to filter 33 | identifiers by identifier type: ``DOI``, ``FTP``, ``GBIF_NODE``, ``GBIF_PARTICIPANT``, 34 | ``GBIF_PORTAL``, ``HANDLER``, ``LSID``, ``UNKNOWN``, ``URI``, ``URL``, ``UUID`` 35 | :param limit: [int] Number of results to return. Default: ``100`` 36 | :param offset: [int] Record to start at. Default: ``0`` 37 | 38 | :return: A dictionary 39 | 40 | References: http://www.gbif.org/developer/registry#installations 41 | 42 | Usage:: 43 | 44 | from pygbif import registry 45 | registry.installations(limit=5) 46 | registry.installations(q="france") 47 | registry.installations(uuid="b77901f9-d9b0-47fa-94e0-dd96450aa2b4") 48 | registry.installations(data='contact', uuid="b77901f9-d9b0-47fa-94e0-dd96450aa2b4") 49 | registry.installations(data='contact', uuid="2e029a0c-87af-42e6-87d7-f38a50b78201") 50 | registry.installations(data='endpoint', uuid="b77901f9-d9b0-47fa-94e0-dd96450aa2b4") 51 | registry.installations(data='dataset', uuid="b77901f9-d9b0-47fa-94e0-dd96450aa2b4") 52 | registry.installations(data='deleted') 53 | registry.installations(data='deleted', limit=2) 54 | registry.installations(data=['deleted','nonPublishing'], limit=2) 55 | registry.installations(identifierType='DOI', limit=2) 56 | """ 57 | args = { 58 | "q": q, 59 | "limit": limit, 60 | "offset": offset, 61 | "identifier": identifier, 62 | "identifierType": identifierType, 63 | } 64 | data_choices = [ 65 | "all", 66 | "contact", 67 | "endpoint", 68 | "dataset", 69 | "identifier", 70 | "tag", 71 | "machineTag", 72 | "comment", 73 | "deleted", 74 | "nonPublishing", 75 | ] 76 | check_data(data, data_choices) 77 | 78 | def getdata(x, uuid, args, **kwargs): 79 | if x not in ["all", "deleted", "nonPublishing"] and uuid is None: 80 | stop( 81 | "You must specify a uuid if data does not equal all and data does not equal one of deleted or nonPublishing" 82 | ) 83 | 84 | if uuid is None: 85 | if x == "all": 86 | url = gbif_baseurl + "installation" 87 | else: 88 | url = gbif_baseurl + "installation/" + x 89 | else: 90 | if x == "all": 91 | url = gbif_baseurl + "installation/" + uuid 92 | else: 93 | url = gbif_baseurl + "installation/" + uuid + "/" + x 94 | 95 | res = gbif_GET(url, args, **kwargs) 96 | return {"meta": get_meta(res), "data": parse_results(res, uuid)} 97 | 98 | if len2(data) == 1: 99 | return getdata(data, uuid, args, **kwargs) 100 | else: 101 | return [getdata(x, uuid, args, **kwargs) for x in data] 102 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | ============================== 2 | pygbif |version| documentation 3 | ============================== 4 | 5 | |pypi| |docs| |ghactions| |coverage| 6 | 7 | Python client for the `GBIF API 8 | `_ 9 | 10 | Source on GitHub at `gbif/pygbif `_ 11 | 12 | Getting help 13 | ============ 14 | 15 | Having trouble? Or want to know how to get started? 16 | 17 | * Try the :doc:`FAQ <../docs/faq>` -- it's got answers to some common questions. 18 | * Looking for specific information? Try the :ref:`genindex` 19 | * Report bugs with pygbif in our `issue tracker`_. 20 | 21 | .. _issue tracker: https://github.com/gbif/pygbif/issues 22 | 23 | 24 | Installation 25 | ============ 26 | 27 | .. toctree:: 28 | :caption: Installation 29 | :hidden: 30 | 31 | intro/install 32 | 33 | :doc:`intro/install` 34 | How to install pygbif. 35 | 36 | 37 | Docs 38 | ==== 39 | 40 | .. toctree:: 41 | :caption: Docs 42 | :hidden: 43 | 44 | docs/faq 45 | docs/usecases 46 | 47 | :doc:`docs/faq` 48 | Frequently asked questions. 49 | 50 | :doc:`docs/usecases` 51 | Usecases for pygbif. 52 | 53 | 54 | Modules 55 | ======= 56 | 57 | .. toctree:: 58 | :caption: Modules 59 | :hidden: 60 | 61 | modules/intro 62 | modules/caching 63 | modules/occurrence 64 | modules/registry 65 | modules/species 66 | modules/maps 67 | modules/utils 68 | modules/literature 69 | modules/collection 70 | modules/institution 71 | 72 | :doc:`modules/intro` 73 | Introduction to pygbif modules. 74 | 75 | :doc:`modules/occurrence` 76 | The occurrence module: core GBIF occurrence data, including count, search, and download APIs. 77 | 78 | :doc:`modules/registry` 79 | The registry module: including datasets, installations, networks, nodes, and organizations. 80 | 81 | :doc:`modules/species` 82 | The species module: including name search, lookup, suggest, usage, and backbone search. 83 | 84 | :doc:`modules/maps` 85 | The maps module: including map. 86 | 87 | :doc:`modules/utils` 88 | The utils module: including wkt_rewind. 89 | 90 | :doc:`modules/literature` 91 | The literature module: including search for literature. 92 | 93 | :doc:`modules/collection` 94 | The collection module: search GRSciColl collections. 95 | 96 | :doc:`modules/institution` 97 | The institution module: search GRSciColl institutions. 98 | 99 | All the rest 100 | ============ 101 | 102 | .. toctree:: 103 | :caption: All the rest 104 | :hidden: 105 | 106 | changelog_link 107 | contributors 108 | contributing 109 | conduct 110 | license 111 | 112 | :doc:`changelog_link` 113 | See what has changed in recent pygbif versions. 114 | 115 | :doc:`contributors` 116 | pygbif contributors. 117 | 118 | :doc:`contributing` 119 | Learn how to contribute to the pygbif project. 120 | 121 | :doc:`conduct` 122 | Expected behavior in this community. By participating in this project you agree to abide by its terms. 123 | 124 | :doc:`license` 125 | The pygbif license. 126 | 127 | Indices and tables 128 | ------------------ 129 | 130 | * :ref:`genindex` 131 | * :ref:`modindex` 132 | * :ref:`search` 133 | 134 | 135 | .. |pypi| image:: https://img.shields.io/pypi/v/pygbif.svg 136 | :target: https://pypi.python.org/pypi/pygbif 137 | 138 | .. |docs| image:: https://readthedocs.org/projects/pygbif/badge/?version=latest 139 | :target: http://pygbif.rtfd.org/ 140 | 141 | .. |ghactions| image:: https://github.com/gbif/pygbif/workflows/Python/badge.svg 142 | :target: https://github.com/gbif/pygbif/actions?query=workflow%3APython 143 | 144 | .. |coverage| image:: https://codecov.io/gh/gbif/pygbif/branch/master/graph/badge.svg?token=frXPREGk1D 145 | :target: https://codecov.io/gh/gbif/pygbif 146 | -------------------------------------------------------------------------------- /pygbif/registry/nodes.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import ( 2 | check_data, 3 | stop, 4 | gbif_baseurl, 5 | gbif_GET, 6 | get_meta, 7 | parse_results, 8 | len2, 9 | ) 10 | 11 | 12 | def nodes( 13 | data="all", 14 | uuid=None, 15 | q=None, 16 | identifier=None, 17 | identifierType=None, 18 | limit=100, 19 | offset=None, 20 | isocode=None, 21 | **kwargs 22 | ): 23 | """ 24 | Nodes metadata. 25 | 26 | :param data: [str] The type of data to get. Default: ``all`` 27 | :param uuid: [str] UUID of the data node provider. This must be specified if data 28 | is anything other than ``all``. 29 | :param q: [str] Query nodes. Only used when ``data = 'all'`` 30 | :param identifier: [fixnum] The value for this parameter can be a simple string or integer, 31 | e.g. identifier=120 32 | :param identifierType: [str] Used in combination with the identifier parameter to filter 33 | identifiers by identifier type: ``DOI``, ``FTP``, ``GBIF_NODE``, ``GBIF_PARTICIPANT``, 34 | ``GBIF_PORTAL``, ``HANDLER``, ``LSID``, ``UNKNOWN``, ``URI``, ``URL``, ``UUID`` 35 | :param limit: [int] Number of results to return. Default: ``100`` 36 | :param offset: [int] Record to start at. Default: ``0`` 37 | :param isocode: [str] A 2 letter country code. Only used if ``data = 'country'``. 38 | 39 | :return: A dictionary 40 | 41 | References http://www.gbif.org/developer/registry#nodes 42 | 43 | Usage:: 44 | 45 | from pygbif import registry 46 | registry.nodes(limit=5) 47 | registry.nodes(identifier=120) 48 | registry.nodes(uuid="1193638d-32d1-43f0-a855-8727c94299d8") 49 | registry.nodes(data='identifier', uuid="03e816b3-8f58-49ae-bc12-4e18b358d6d9") 50 | registry.nodes(data=['identifier','organization','comment'], uuid="03e816b3-8f58-49ae-bc12-4e18b358d6d9") 51 | 52 | uuids = ["8cb55387-7802-40e8-86d6-d357a583c596","02c40d2a-1cba-4633-90b7-e36e5e97aba8", 53 | "7a17efec-0a6a-424c-b743-f715852c3c1f","b797ce0f-47e6-4231-b048-6b62ca3b0f55", 54 | "1193638d-32d1-43f0-a855-8727c94299d8","d3499f89-5bc0-4454-8cdb-60bead228a6d", 55 | "cdc9736d-5ff7-4ece-9959-3c744360cdb3","a8b16421-d80b-4ef3-8f22-098b01a89255", 56 | "8df8d012-8e64-4c8a-886e-521a3bdfa623","b35cf8f1-748d-467a-adca-4f9170f20a4e", 57 | "03e816b3-8f58-49ae-bc12-4e18b358d6d9","073d1223-70b1-4433-bb21-dd70afe3053b", 58 | "07dfe2f9-5116-4922-9a8a-3e0912276a72","086f5148-c0a8-469b-84cc-cce5342f9242", 59 | "0909d601-bda2-42df-9e63-a6d51847ebce","0e0181bf-9c78-4676-bdc3-54765e661bb8", 60 | "109aea14-c252-4a85-96e2-f5f4d5d088f4","169eb292-376b-4cc6-8e31-9c2c432de0ad", 61 | "1e789bc9-79fc-4e60-a49e-89dfc45a7188","1f94b3ca-9345-4d65-afe2-4bace93aa0fe"] 62 | 63 | [ registry.nodes(data='identifier', uuid=x) for x in uuids ] 64 | """ 65 | args = { 66 | "q": q, 67 | "limit": limit, 68 | "offset": offset, 69 | "identifier": identifier, 70 | "identifierType": identifierType, 71 | } 72 | data_choices = [ 73 | "all", 74 | "organization", 75 | "endpoint", 76 | "identifier", 77 | "tag", 78 | "machineTag", 79 | "comment", 80 | "pendingEndorsement", 81 | "country", 82 | "dataset", 83 | "installation", 84 | ] 85 | check_data(data, data_choices) 86 | 87 | def getdata(x, uuid, args, **kwargs): 88 | if x != "all" and uuid is None: 89 | stop('You must specify a uuid if data does not equal "all"') 90 | 91 | if uuid is None: 92 | if x == "all": 93 | url = gbif_baseurl + "node" 94 | else: 95 | if isocode is not None and x == "country": 96 | url = gbif_baseurl + "node/country/" + isocode 97 | else: 98 | url = gbif_baseurl + "node/" + x 99 | else: 100 | if x == "all": 101 | url = gbif_baseurl + "node/" + uuid 102 | else: 103 | url = gbif_baseurl + "node/" + uuid + "/" + x 104 | 105 | res = gbif_GET(url, args, **kwargs) 106 | return {"meta": get_meta(res), "data": parse_results(res, uuid)} 107 | 108 | # Get data 109 | if len2(data) == 1: 110 | return getdata(data, uuid, args, **kwargs) 111 | else: 112 | return [getdata(x, uuid, args, **kwargs) for x in data] 113 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_dataset_metrics_multiple_uuids.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/dataset/3f8a1297-3259-4700-91fc-acc4170b27ce/metrics 15 | response: 16 | body: 17 | string: '{"key":3171613,"datasetKey":"3f8a1297-3259-4700-91fc-acc4170b27ce","usagesCount":33312,"synonymsCount":22536,"distinctNamesCount":33282,"nubMatchingCount":31698,"colMatchingCount":28003,"nubCoveragePct":95,"colCoveragePct":84,"countByConstituent":{},"countByKingdom":{"PLANTAE":9369},"countByRank":{"SPECIES":6327,"GENUS":1284,"VARIETY":907,"SUBSPECIES":723,"SECTION":472,"TRIBE":332,"FAMILY":182,"SUBFAMILY":164,"SUBGENUS":140,"SUBTRIBE":69,"SUBSECTION":61,"ORDER":56,"SERIES":39,"SUPERORDER":13,"SUBCLASS":6,"CLASS":1},"countNamesByLanguage":{"ENGLISH":21095,"FRENCH":11592},"countExtRecordsByExtension":{"VERNACULAR_NAME":32687,"DISTRIBUTION":30141,"DESCRIPTION":10759,"IDENTIFIER":0,"MULTIMEDIA":0,"REFERENCE":0,"SPECIES_PROFILE":0,"TYPES_AND_SPECIMEN":0},"countByOrigin":{"SOURCE":33304,"PROPARTE":8},"countByIssue":{"SCIENTIFIC_NAME_ASSEMBLED":1951,"BACKBONE_MATCH_NONE":1614,"PARTIALLY_PARSABLE":44,"UNPARSABLE":3},"otherCount":{},"downloaded":"2024-08-29T19:02:34.364+00:00"}' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '0' 23 | Cache-Control: 24 | - public, max-age=3601 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '983' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:13:30 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 782959276 805307034 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | - request: 53 | body: null 54 | headers: 55 | Accept: 56 | - '*/*' 57 | Accept-Encoding: 58 | - gzip, deflate 59 | Connection: 60 | - keep-alive 61 | user-agent: 62 | - python-requests/2.31.0,pygbif/0.6.6 63 | method: GET 64 | uri: https://api.gbif.org/v1/dataset/66dd0960-2d7d-46ee-a491-87b9adcfe7b1/metrics 65 | response: 66 | body: 67 | string: '{"key":6224722,"datasetKey":"66dd0960-2d7d-46ee-a491-87b9adcfe7b1","usagesCount":138248,"synonymsCount":59906,"distinctNamesCount":138122,"nubMatchingCount":131902,"colMatchingCount":127695,"nubCoveragePct":95,"colCoveragePct":92,"countByConstituent":{},"countByKingdom":{"PLANTAE":72383,"INCERTAE_SEDIS":1},"countByRank":{"SPECIES":52563,"GENUS":12880,"VARIETY":4810,"SUBSPECIES":4462,"SERIES":1077,"TRIBE":844,"FAMILY":509,"SUBTRIBE":327,"SUBFAMILY":303,"SUBGENUS":240,"FORM":238,"SECTION":82,"SUBVARIETY":5,"KINGDOM":1},"countNamesByLanguage":{"ENGLISH":32165,"SWEDISH":7284,"GERMAN":4882,"SPANISH":4157,"FRENCH":3783,"PORTUGUESE":1141,"ITALIAN":663,"AFRIKAANS":450,"ARABIC":182,"INDONESIAN":153,"MALAY":139,"SWAHILI":72,"DUTCH":50,"MALAGASY":41,"DANISH":21,"FIJIAN":17,"HINDI":15,"SOMALI":11,"TURKISH":8,"NEPALI":7,"POLISH":7,"CZECH":4,"KOREAN":4,"TAJIK":4,"CATALAN":3,"FINNISH":3,"JAVANESE":3,"ROMANIAN":3,"VIETNAMESE":3,"YORUBA":3,"AYMARA":2,"CHINESE":2,"SAMOAN":2,"ALBANIAN":1,"BISLAMA":1,"HUNGARIAN":1,"LAO":1,"MONGOLIAN":1,"NORWEGIAN":1,"QUECHUA":1,"RUSSIAN":1,"SANSKRIT":1,"TAMIL":1,"THAI":1,"TONGA":1,"TURKMEN":1},"countExtRecordsByExtension":{"VERNACULAR_NAME":64900,"DESCRIPTION":0,"DISTRIBUTION":0,"IDENTIFIER":0,"MULTIMEDIA":0,"REFERENCE":0,"SPECIES_PROFILE":0,"TYPES_AND_SPECIMEN":0},"countByOrigin":{"SOURCE":138246,"DENORMED_CLASSIFICATION":1,"MISSING_ACCEPTED":1},"countByIssue":{"SCIENTIFIC_NAME_ASSEMBLED":128661,"BACKBONE_MATCH_NONE":6346,"VERNACULAR_NAME_INVALID":5501,"CHAINED_SYNOYM":522,"PARENT_NAME_USAGE_ID_INVALID":48,"PARTIALLY_PARSABLE":7,"PARENT_CYCLE":1},"otherCount":{},"downloaded":"2025-11-04T10:08:29.160+00:00"}' 68 | headers: 69 | Accept-Ranges: 70 | - bytes 71 | Age: 72 | - '0' 73 | Cache-Control: 74 | - public, max-age=3601 75 | Connection: 76 | - keep-alive 77 | Content-Length: 78 | - '1649' 79 | Content-Type: 80 | - application/json 81 | Date: 82 | - Fri, 14 Nov 2025 10:13:31 GMT 83 | Expires: 84 | - '0' 85 | Pragma: 86 | - no-cache 87 | Vary: 88 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 89 | Via: 90 | - 1.1 varnish (Varnish/6.6) 91 | X-Content-Type-Options: 92 | - nosniff 93 | X-Frame-Options: 94 | - DENY 95 | X-Varnish: 96 | - '797508356' 97 | X-XSS-Protection: 98 | - 1; mode=block 99 | status: 100 | code: 200 101 | message: OK 102 | version: 1 103 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at myrmecocystus@gmail.com. All complaints will be reviewed and investigated promptly and fairly. 63 | 64 | All community leaders are obligated to respect the privacy and security of the 65 | reporter of any incident. 66 | 67 | ## Enforcement Guidelines 68 | 69 | Community leaders will follow these Community Impact Guidelines in determining 70 | the consequences for any action they deem in violation of this Code of Conduct: 71 | 72 | ### 1. Correction 73 | 74 | **Community Impact**: Use of inappropriate language or other behavior deemed 75 | unprofessional or unwelcome in the community. 76 | 77 | **Consequence**: A private, written warning from community leaders, providing 78 | clarity around the nature of the violation and an explanation of why the 79 | behavior was inappropriate. A public apology may be requested. 80 | 81 | ### 2. Warning 82 | 83 | **Community Impact**: A violation through a single incident or series 84 | of actions. 85 | 86 | **Consequence**: A warning with consequences for continued behavior. No 87 | interaction with the people involved, including unsolicited interaction with 88 | those enforcing the Code of Conduct, for a specified period of time. This 89 | includes avoiding interactions in community spaces as well as external channels 90 | like social media. Violating these terms may lead to a temporary or 91 | permanent ban. 92 | 93 | ### 3. Temporary Ban 94 | 95 | **Community Impact**: A serious violation of community standards, including 96 | sustained inappropriate behavior. 97 | 98 | **Consequence**: A temporary ban from any sort of interaction or public 99 | communication with the community for a specified period of time. No public or 100 | private interaction with the people involved, including unsolicited interaction 101 | with those enforcing the Code of Conduct, is allowed during this period. 102 | Violating these terms may lead to a permanent ban. 103 | 104 | ### 4. Permanent Ban 105 | 106 | **Community Impact**: Demonstrating a pattern of violation of community 107 | standards, including sustained inappropriate behavior, harassment of an 108 | individual, or aggression toward or disparagement of classes of individuals. 109 | 110 | **Consequence**: A permanent ban from any sort of public interaction within 111 | the community. 112 | 113 | ## Attribution 114 | 115 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 116 | version 2.0, available at 117 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 118 | 119 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 120 | enforcement ladder](https://github.com/mozilla/diversity). 121 | 122 | [homepage]: https://www.contributor-covenant.org 123 | -------------------------------------------------------------------------------- /pygbif/gbifutils.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import pygbif 4 | 5 | # import requests_cache 6 | # from requests_cache.core import remove_expired_responses 7 | # import os.path 8 | # import tempfile 9 | 10 | # CACHE_FILE = os.path.join(tempfile.gettempdir(), 'pygbif_requests_cache') 11 | # expire = 300 12 | # backend = "sqlite" 13 | # requests_cache.install_cache(cache_name=CACHE_FILE, backend=backend, expire_after=expire) 14 | # remove_expired_responses() 15 | 16 | 17 | class NoResultException(Exception): 18 | pass 19 | 20 | 21 | def gbif_search_GET(url, args, **kwargs): 22 | # if args['geometry'] != None: 23 | # if args['geometry'].__class__ == list: 24 | # b = args['geometry'] 25 | # args['geometry'] = geometry.box(b[0], b[1], b[2], b[3]).wkt 26 | out = requests.get(url, params=args, **kwargs) 27 | out.raise_for_status() 28 | stopifnot(out.headers["content-type"]) 29 | return out.json() 30 | 31 | 32 | def gbif_GET(url, args, **kwargs): 33 | out = requests.get(url, params=args, headers=make_ua(), **kwargs) 34 | out.raise_for_status() 35 | stopifnot(out.headers["content-type"]) 36 | return out.json() 37 | 38 | def gbif_GET_raw(url): 39 | out = requests.get(url) 40 | return out.content 41 | 42 | def gbif_GET_map(url, args, ctype, **kwargs): 43 | out = requests.get(url, params=args, headers=make_ua(), **kwargs) 44 | out.raise_for_status() 45 | stopifnot(out.headers["content-type"], ctype) 46 | return out 47 | 48 | 49 | def gbif_GET_write(url, path, **kwargs): 50 | out = requests.get(url, headers=make_ua(), stream=True, **kwargs) 51 | out.raise_for_status() 52 | if out.status_code == 200: 53 | with open(path, "wb") as f: 54 | for chunk in out.iter_content(chunk_size=1024): 55 | if chunk: 56 | f.write(chunk) 57 | # FIXME: removing response content-type check for now, maybe add later 58 | # ctype = "application/octet-stream" 59 | # if not re.match(ctype, out.headers["content-type"]): 60 | # raise NoResultException("content-type did not contain '%s'" % ctype) 61 | return path 62 | 63 | 64 | def gbif_POST(url, body, **kwargs): 65 | head = make_ua() 66 | out = requests.post(url, json=body, headers=head, **kwargs) 67 | out.raise_for_status() 68 | stopifnot(out.headers["content-type"]) 69 | return out.json() 70 | 71 | 72 | def gbif_DELETE(url, body, **kwargs): 73 | head = make_ua() 74 | out = requests.delete(url, json=False, headers=head, **kwargs) 75 | out.raise_for_status() 76 | return out.status_code == 204 77 | 78 | 79 | def stopifnot(x, ctype="application/json"): 80 | if x != ctype: 81 | raise NoResultException("content-type did not equal " + ctype) 82 | 83 | 84 | def stop(x): 85 | raise ValueError(x) 86 | 87 | 88 | def make_ua(): 89 | return { 90 | "user-agent": "python-requests/" 91 | + requests.__version__ 92 | + ",pygbif/" 93 | + pygbif.__version__ 94 | } 95 | 96 | 97 | def is_none(x): 98 | return x.__class__.__name__ == "NoneType" 99 | 100 | 101 | def is_not_none(x): 102 | return x.__class__.__name__ != "NoneType" 103 | 104 | 105 | gbif_baseurl = "https://api.gbif.org/v1/" 106 | 107 | requests_argset = [ 108 | "timeout", 109 | "cookies", 110 | "auth", 111 | "allow_redirects", 112 | "proxies", 113 | "verify", 114 | "stream", 115 | "cert", 116 | ] 117 | 118 | 119 | def bn(x): 120 | if x: 121 | return x 122 | else: 123 | return None 124 | 125 | 126 | def parse_results(x, y): 127 | if y.__class__.__name__ != "NoneType": 128 | if y.__class__ != dict: 129 | return x 130 | else: 131 | if "endOfRecords" in x.keys(): 132 | return x["results"] 133 | else: 134 | return x 135 | else: 136 | return x["results"] 137 | 138 | 139 | def check_data(x, y): 140 | if len2(x) == 1: 141 | testdata = [x] 142 | else: 143 | testdata = x 144 | 145 | for z in testdata: 146 | if z not in y: 147 | raise TypeError(z + " is not one of the choices") 148 | 149 | 150 | def len2(x): 151 | if isinstance(x, int): 152 | return len([x]) 153 | elif isinstance(x, str): 154 | return len([x]) 155 | else: 156 | return len(x) 157 | 158 | 159 | def stuff(**kwargs): 160 | return kwargs 161 | 162 | 163 | def check_param_lens(**kwargs): 164 | tmp = {k: v for k, v in kwargs.items() if v is not None} 165 | for k, v in tmp.items(): 166 | if len2(v) > 1: 167 | raise TypeError(k + " must be length 1") 168 | 169 | 170 | def get_meta(x): 171 | if has_meta(x): 172 | return {z: x[z] for z in ["offset", "limit", "endOfRecords"]} 173 | else: 174 | return None 175 | 176 | 177 | def has_meta(x): 178 | if x.__class__ != dict: 179 | return False 180 | else: 181 | tmp = [y in x.keys() for y in ["offset", "limit", "endOfRecords"]] 182 | return True in tmp 183 | 184 | 185 | def has(str, pattern): 186 | w = re.search(pattern, str) 187 | return w is not None 188 | 189 | 190 | def bool2str(x): 191 | if x is not None: 192 | z = str(x).lower() 193 | return z 194 | else: 195 | return x 196 | -------------------------------------------------------------------------------- /pygbif/occurrences/count.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import gbif_baseurl, bool2str, gbif_GET, check_param_lens 2 | 3 | 4 | def count( 5 | taxonKey=None, 6 | basisOfRecord=None, 7 | country=None, 8 | isGeoreferenced=None, 9 | datasetKey=None, 10 | publishingCountry=None, 11 | typeStatus=None, 12 | issue=None, 13 | year=None, 14 | **kwargs 15 | ): 16 | """ 17 | Returns occurrence counts for a predefined set of dimensions 18 | 19 | For all parameters below, only one value allowed per function call. 20 | See :func:`~occurrences.search` for passing more than one value 21 | per parameter. 22 | 23 | :param taxonKey: [int] A GBIF occurrence identifier 24 | :param basisOfRecord: [str] A GBIF occurrence identifier 25 | :param country: [str] A GBIF occurrence identifier 26 | :param isGeoreferenced: [bool] A GBIF occurrence identifier 27 | :param datasetKey: [str] A GBIF occurrence identifier 28 | :param publishingCountry: [str] A GBIF occurrence identifier 29 | :param typeStatus: [str] A GBIF occurrence identifier 30 | :param issue: [str] A GBIF occurrence identifier 31 | :param year: [int] A GBIF occurrence identifier 32 | 33 | :return: dict 34 | 35 | Usage:: 36 | 37 | from pygbif import occurrences 38 | occurrences.count(taxonKey = 3329049) 39 | occurrences.count(country = 'CA') 40 | occurrences.count(isGeoreferenced = True) 41 | occurrences.count(basisOfRecord = 'OBSERVATION') 42 | """ 43 | check_param_lens( 44 | taxonKey=taxonKey, 45 | basisOfRecord=basisOfRecord, 46 | country=country, 47 | isGeoreferenced=isGeoreferenced, 48 | datasetKey=datasetKey, 49 | publishingCountry=publishingCountry, 50 | typeStatus=typeStatus, 51 | issue=issue, 52 | year=year, 53 | ) 54 | url = gbif_baseurl + "occurrence/count" 55 | isGeoreferenced = bool2str(isGeoreferenced) 56 | out = gbif_GET( 57 | url, 58 | { 59 | "taxonKey": taxonKey, 60 | "basisOfRecord": basisOfRecord, 61 | "country": country, 62 | "isGeoreferenced": isGeoreferenced, 63 | "datasetKey": datasetKey, 64 | "publishingCountry": publishingCountry, 65 | "typeStatus": typeStatus, 66 | "issue": issue, 67 | "year": year, 68 | }, 69 | **kwargs 70 | ) 71 | return out 72 | 73 | 74 | def count_basisofrecord(**kwargs): 75 | """ 76 | Lists occurrence counts by basis of record. 77 | 78 | :return: dict 79 | 80 | Usage:: 81 | 82 | from pygbif import occurrences 83 | occurrences.count_basisofrecord() 84 | """ 85 | url = gbif_baseurl + "occurrence/counts/basisOfRecord" 86 | out = gbif_GET(url, {}, **kwargs) 87 | return out 88 | 89 | 90 | def count_year(year, **kwargs): 91 | """ 92 | Lists occurrence counts by year 93 | 94 | :param year: [int] year range, e.g., ``1990,2000``. Does not support ranges like ``asterisk,2010`` 95 | 96 | :return: dict 97 | 98 | Usage:: 99 | 100 | from pygbif import occurrences 101 | occurrences.count_year(year = '1990,2000') 102 | """ 103 | url = gbif_baseurl + "occurrence/counts/year" 104 | out = gbif_GET(url, {"year": year}, **kwargs) 105 | return out 106 | 107 | 108 | def count_datasets(taxonKey=None, country=None, **kwargs): 109 | """ 110 | Lists occurrence counts for datasets that cover a given taxon or country 111 | 112 | :param taxonKey: [int] Taxon key 113 | :param country: [str] A country, two letter code 114 | 115 | :return: dict 116 | 117 | Usage:: 118 | 119 | from pygbif import occurrences 120 | occurrences.count_datasets(country = "DE") 121 | """ 122 | url = gbif_baseurl + "occurrence/counts/datasets" 123 | out = gbif_GET(url, {"taxonKey": taxonKey, "country": country}, **kwargs) 124 | return out 125 | 126 | 127 | def count_countries(publishingCountry, **kwargs): 128 | """ 129 | Lists occurrence counts for all countries covered by the data published by the given country 130 | 131 | :param publishingCountry: [str] A two letter country code 132 | 133 | :return: dict 134 | 135 | Usage:: 136 | 137 | from pygbif import occurrences 138 | occurrences.count_countries(publishingCountry = "DE") 139 | """ 140 | url = gbif_baseurl + "occurrence/counts/countries" 141 | out = gbif_GET(url, {"publishingCountry": publishingCountry}, **kwargs) 142 | return out 143 | 144 | 145 | def count_publishingcountries(country, **kwargs): 146 | """ 147 | Lists occurrence counts for all countries that publish data about the given country 148 | 149 | :param country: [str] A country, two letter code 150 | 151 | :return: dict 152 | 153 | Usage:: 154 | 155 | from pygbif import occurrences 156 | occurrences.count_publishingcountries(country = "DE") 157 | """ 158 | url = gbif_baseurl + "occurrence/counts/publishingCountries" 159 | out = gbif_GET(url, {"country": country}, **kwargs) 160 | return out 161 | 162 | 163 | def count_schema(**kwargs): 164 | """ 165 | List the supported metrics by the service 166 | 167 | :return: dict 168 | 169 | Usage:: 170 | 171 | from pygbif import occurrences 172 | occurrences.count_schema() 173 | """ 174 | url = gbif_baseurl + "occurrence/count/schema" 175 | out = gbif_GET(url, {}, **kwargs) 176 | return out 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pygbif 2 | ====== 3 | 4 | |pypi| |docs| |ghactions| |coverage| |black| 5 | 6 | Python client for the `GBIF API `_ 7 | 8 | `Source on GitHub at gbif/pygbif `_ 9 | 10 | Other GBIF clients: 11 | 12 | * R: `rgbif`, `ropensci/rgbif `_ 13 | * Ruby: `gbifrb`, `sckott/gbifrb `_ 14 | * PHP: `php-gbif`, `restelae/php-gbif `_ 15 | 16 | Contributing: `CONTRIBUTING.md `_ 17 | 18 | Installation 19 | ============ 20 | 21 | Stable from pypi 22 | 23 | .. code-block:: console 24 | 25 | pip install pygbif 26 | 27 | Development version 28 | 29 | .. code-block:: console 30 | 31 | [sudo] pip install git+git://github.com/gbif/pygbif.git#egg=pygbif 32 | 33 | 34 | `pygbif` is split up into modules for each of the major groups of API methods. 35 | 36 | * Registry - Datasets, Nodes, Installations, Networks, Organizations 37 | * Species - Taxonomic names 38 | * Occurrences - Occurrence data, including the download API 39 | * Maps - Maps, get raster maps from GBIF as png or mvt 40 | 41 | You can import the entire library, or each module individually as needed. 42 | 43 | In addition there is a utils module, currently with one method: `wkt_rewind`, and 44 | a `caching` method to manage whether HTTP requests are cached or not. See `?pygbif.caching`. 45 | 46 | Registry module 47 | =============== 48 | 49 | registry module API: 50 | 51 | * `organizations` 52 | * `nodes` 53 | * `networks` 54 | * `installations` 55 | * `datasets` 56 | * `dataset_metrics` 57 | * `dataset_suggest` 58 | * `dataset_search` 59 | 60 | Example usage: 61 | 62 | .. code-block:: python 63 | 64 | from pygbif import registry 65 | registry.dataset_metrics(uuid='3f8a1297-3259-4700-91fc-acc4170b27ce') 66 | 67 | Species module 68 | ============== 69 | 70 | species module API: 71 | 72 | * `name_backbone` 73 | * `name_suggest` 74 | * `name_usage` 75 | * `name_lookup` 76 | * `name_parser` 77 | 78 | Example usage: 79 | 80 | .. code-block:: python 81 | 82 | from pygbif import species 83 | species.name_suggest(q='Puma concolor') 84 | 85 | Occurrences module 86 | ================== 87 | 88 | occurrences module API: 89 | 90 | * `search` 91 | * `get` 92 | * `get_verbatim` 93 | * `get_fragment` 94 | * `count` 95 | * `count_basisofrecord` 96 | * `count_year` 97 | * `count_datasets` 98 | * `count_countries` 99 | * `count_schema` 100 | * `count_publishingcountries` 101 | * `download` 102 | * `download_meta` 103 | * `download_list` 104 | * `download_get` 105 | * `download_citation` 106 | * `download_describe` 107 | * `download_sql` 108 | 109 | Example usage: 110 | 111 | .. code-block:: python 112 | 113 | from pygbif import occurrences as occ 114 | occ.search(taxonKey = 3329049) 115 | occ.get(key = 252408386) 116 | occ.count(isGeoreferenced = True) 117 | occ.download('basisOfRecord = PRESERVED_SPECIMEN') 118 | occ.download('taxonKey = 3119195') 119 | occ.download('decimalLatitude > 50') 120 | occ.download_list(user = "sckott", limit = 5) 121 | occ.download_meta(key = "0000099-140929101555934") 122 | occ.download_get("0000066-140928181241064") 123 | occ.download_citation("0002526-241107131044228") 124 | occ.download_describe("simpleCsv") 125 | occ.download_sql("SELECT gbifid,countryCode FROM occurrence WHERE genusKey = 2435098") 126 | 127 | Maps module 128 | =========== 129 | 130 | maps module API: 131 | 132 | * `map` 133 | 134 | Example usage: 135 | 136 | .. code-block:: python 137 | 138 | from pygbif import maps 139 | out = maps.map(taxonKey = 212, year = 1998, bin = "hex", 140 | hexPerTile = 30, style = "classic-noborder.poly") 141 | out.response 142 | out.path 143 | out.img 144 | out.plot() 145 | 146 | .. image:: https://github.com/gbif/pygbif/raw/master/gbif_map.png 147 | :width: 25% 148 | 149 | utils module 150 | ============ 151 | 152 | utils module API: 153 | 154 | * `wkt_rewind` 155 | 156 | Example usage: 157 | 158 | .. code-block:: python 159 | 160 | from pygbif import utils 161 | x = 'POLYGON((144.6 13.2, 144.6 13.6, 144.9 13.6, 144.9 13.2, 144.6 13.2))' 162 | utils.wkt_rewind(x) 163 | 164 | 165 | 166 | Contributors 167 | ============ 168 | 169 | * `Scott Chamberlain `_ 170 | * `Robert Forkel `_ 171 | * `Jan Legind `_ 172 | * `Stijn Van Hoey `_ 173 | * `Peter Desmet `_ 174 | * `Nicolas Noé `_ 175 | 176 | Meta 177 | ==== 178 | 179 | * License: MIT, see `LICENSE file `_ 180 | * Please note that this project is released with a `Contributor Code of Conduct `_. By participating in this project you agree to abide by its terms. 181 | 182 | .. |pypi| image:: https://img.shields.io/pypi/v/pygbif.svg 183 | :target: https://pypi.python.org/pypi/pygbif 184 | 185 | .. |docs| image:: https://readthedocs.org/projects/pygbif/badge/?version=latest 186 | :target: http://pygbif.rtfd.org/ 187 | 188 | .. |ghactions| image:: https://github.com/gbif/pygbif/workflows/Python/badge.svg 189 | :target: https://github.com/gbif/pygbif/actions?query=workflow%3APython 190 | 191 | .. |coverage| image:: https://codecov.io/gh/gbif/pygbif/branch/master/graph/badge.svg?token=frXPREGk1D 192 | :target: https://codecov.io/gh/gbif/pygbif 193 | 194 | .. |black| image:: https://img.shields.io/badge/code%20style-black-000000.svg 195 | :target: https://github.com/psf/black 196 | 197 | 198 | -------------------------------------------------------------------------------- /pygbif/species/name_usage.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import check_data, len2, gbif_baseurl, gbif_GET 2 | 3 | 4 | def name_usage( 5 | key=None, 6 | name=None, 7 | data="all", 8 | language=None, 9 | datasetKey=None, 10 | uuid=None, 11 | sourceId=None, 12 | rank=None, 13 | shortname=None, 14 | limit=100, 15 | offset=None, 16 | **kwargs 17 | ): 18 | """ 19 | Lookup details for specific names in all taxonomies in GBIF. 20 | 21 | :param key: [fixnum] A GBIF key for a taxon 22 | :param name: [str] Filters by a case insensitive, canonical namestring, 23 | e.g. 'Puma concolor' 24 | :param data: [str] The type of data to get. Default: ``all``. Options: ``all``, 25 | ``verbatim``, ``name``, ``parents``, ``children``, 26 | ``related``, ``synonyms``, ``descriptions``, ``distributions``, ``media``, 27 | ``references``, ``speciesProfiles``, ``vernacularNames``, ``typeSpecimens``, 28 | ``root`` 29 | :param language: [str] Language. Expects a ISO 639-1 language codes using 2 lower 30 | case letters. Languages returned are 3 letter codes. The language parameter 31 | only applies to the ``/species``, ``/species/{int}``, 32 | ``/species/{int}/parents``, ``/species/{int}/children``, ``/species/{int}/related``, 33 | ``/species/{int}/synonyms`` routes (here routes are determined by the ``data`` 34 | parameter). 35 | :param datasetKey: [str] Filters by the dataset's key (a uuid) 36 | :param uuid: [str] A uuid for a dataset. Should give exact same results as datasetKey. 37 | :param sourceId: [fixnum] Filters by the source identifier. 38 | :param rank: [str] Taxonomic rank. Filters by taxonomic rank as one of: 39 | ``CLASS``, ``CULTIVAR``, ``CULTIVAR_GROUP``, ``DOMAIN``, ``FAMILY``, ``FORM``, ``GENUS``, ``INFORMAL``, 40 | ``INFRAGENERIC_NAME``, ``INFRAORDER``, ``INFRASPECIFIC_NAME``, ``INFRASUBSPECIFIC_NAME``, 41 | ``KINGDOM``, ``ORDER``, ``PHYLUM``, ``SECTION``, ``SERIES``, ``SPECIES``, ``STRAIN``, ``SUBCLASS``, ``SUBFAMILY``, 42 | ``SUBFORM``, ``SUBGENUS``, ``SUBKINGDOM``, ``SUBORDER``, ``SUBPHYLUM``, ``SUBSECTION``, ``SUBSERIES``, 43 | ``SUBSPECIES``, ``SUBTRIBE``, ``SUBVARIETY``, ``SUPERCLASS``, ``SUPERFAMILY``, ``SUPERORDER``, 44 | ``SUPERPHYLUM``, ``SUPRAGENERIC_NAME``, ``TRIBE``, ``UNRANKED``, ``VARIETY`` 45 | :param shortname: [str] A short name..need more info on this? 46 | :param limit: [fixnum] Number of records to return. Default: ``100``. Maximum: ``1000``. (optional) 47 | :param offset: [fixnum] Record number to start at. (optional) 48 | 49 | References: See http://www.gbif.org/developer/species#nameUsages for details 50 | 51 | Usage:: 52 | 53 | from pygbif import species 54 | 55 | species.name_usage(key=1) 56 | 57 | # Name usage for a taxonomic name 58 | species.name_usage(name='Puma', rank="GENUS") 59 | 60 | # All name usages 61 | species.name_usage() 62 | 63 | # References for a name usage 64 | species.name_usage(key=2435099, data='references') 65 | 66 | # Species profiles, descriptions 67 | species.name_usage(key=5231190, data='speciesProfiles') 68 | species.name_usage(key=5231190, data='descriptions') 69 | species.name_usage(key=2435099, data='children') 70 | 71 | # Vernacular names for a name usage 72 | species.name_usage(key=5231190, data='vernacularNames') 73 | 74 | # Limit number of results returned 75 | species.name_usage(key=5231190, data='vernacularNames', limit=3) 76 | 77 | # Search for names by dataset with datasetKey parameter 78 | species.name_usage(datasetKey="d7dddbf4-2cf0-4f39-9b2a-bb099caae36c") 79 | """ 80 | args = { 81 | "language": language, 82 | "name": name, 83 | "datasetKey": datasetKey, 84 | "rank": rank, 85 | "sourceId": sourceId, 86 | "limit": limit, 87 | "offset": offset, 88 | } 89 | data_choices = [ 90 | "all", 91 | "verbatim", 92 | "name", 93 | "parents", 94 | "children", 95 | "related", 96 | "synonyms", 97 | "descriptions", 98 | "distributions", 99 | "media", 100 | "references", 101 | "speciesProfiles", 102 | "vernacularNames", 103 | "typeSpecimens", 104 | "root", 105 | ] 106 | check_data(data, data_choices) 107 | if len2(data) == 1: 108 | return name_usage_fetch(data, key, shortname, uuid, args, **kwargs) 109 | else: 110 | return [name_usage_fetch(x, key, shortname, uuid, args, **kwargs) for x in data] 111 | 112 | 113 | def name_usage_fetch(x, key, shortname, uuid, args, **kwargs): 114 | if x != "all" and key is None: 115 | raise TypeError("You must specify `key` if `data` does not equal `all`") 116 | 117 | if x == "all" and key is None: 118 | url = gbif_baseurl + "species" 119 | else: 120 | if x == "all" and key is not None: 121 | url = gbif_baseurl + "species/" + str(key) 122 | else: 123 | if x in [ 124 | "verbatim", 125 | "name", 126 | "parents", 127 | "children", 128 | "related", 129 | "synonyms", 130 | "descriptions", 131 | "distributions", 132 | "media", 133 | "references", 134 | "speciesProfiles", 135 | "vernacularNames", 136 | "typeSpecimens", 137 | ]: 138 | url = gbif_baseurl + "species/%s/%s" % (str(key), x) 139 | else: 140 | if x == "root": 141 | url = gbif_baseurl + "species/%s/%s" % (uuid, shortname) 142 | 143 | res = gbif_GET(url, args, **kwargs) 144 | return res 145 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count_countries.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/counts/countries?publishingCountry=DE 15 | response: 16 | body: 17 | string: "{\"GERMANY\":58308282,\"UNITED_STATES\":6058357,\"AUSTRALIA\":1442425,\"UNITED_KINGDOM\":1106982,\"CHINA\":967375,\"SPAIN\":511184,\"ITALY\":449911,\"CANADA\":423818,\"SAUDI_ARABIA\":399350,\"AUSTRIA\":373142,\"GREECE\":368827,\"MEXICO\":341795,\"BELGIUM\":304813,\"FRANCE\":297311,\"NETHERLANDS\":237841,\"CHILE\":227206,\"ANTARCTICA\":220748,\"COLOMBIA\":205246,\"BRAZIL\":191712,\"SWITZERLAND\":191006,\"SWEDEN\":187381,\"ARGENTINA\":176252,\"PORTUGAL\":156772,\"RUSSIAN_FEDERATION\":140768,\"DENMARK\":140143,\"SOUTH_AFRICA\":138495,\"SVALBARD_JAN_MAYEN\":136359,\"POLAND\":125019,\"NORWAY\":105744,\"KENYA\":104320,\"JAPAN\":97780,\"NAMIBIA\":95460,\"CZECH_REPUBLIC\":94549,\"PAPUA_NEW_GUINEA\":87605,\"PERU\":84206,\"TANZANIA\":82241,\"PHILIPPINES\":81566,\"INDONESIA\":80111,\"EL_SALVADOR\":79034,\"TURKEY\":75366,\"NEW_ZEALAND\":73769,\"ECUADOR\":70682,\"UNKNOWN\":68398,\"PANAMA\":66815,\"COSTA_RICA\":65073,\"GREENLAND\":60996,\"VENEZUELA\":60642,\"HONG_KONG\":57452,\"BURKINA_FASO\":55823,\"TAIWAN\":53991,\"INDIA\":53637,\"MADAGASCAR\":53133,\"UKRAINE\":50697,\"OMAN\":46925,\"IRELAND\":46722,\"CROATIA\":44379,\"MOROCCO\":43723,\"URUGUAY\":42971,\"VIETNAM\":42837,\"ROMANIA\":42720,\"BAHAMAS\":41488,\"HUNGARY\":40234,\"YEMEN\":38763,\"PUERTO_RICO\":36877,\"MARSHALL_ISLANDS\":35215,\"MALAYSIA\":32268,\"CUBA\":32027,\"BOLIVIA\":31667,\"NICARAGUA\":31354,\"HEARD_MCDONALD_ISLANDS\":30207,\"CAMEROON\":27976,\"ICELAND\":27893,\"IRAN\":27831,\"GEORGIA\":26566,\"MALDIVES\":25386,\"WESTERN_SAHARA\":25282,\"EGYPT\":25273,\"BULGARIA\":25201,\"ETHIOPIA\":25096,\"GUATEMALA\":25088,\"THAILAND\":23722,\"CYPRUS\":23306,\"GHANA\":21847,\"SEYCHELLES\":20452,\"ISRAEL\":19236,\"SRI_LANKA\":19130,\"VANUATU\":18006,\"ANGOLA\":17594,\"CAPE_VERDE\":17580,\"SENEGAL\":17117,\"SLOVENIA\":16070,\"SLOVAKIA\":15030,\"FINLAND\":14762,\"MALAWI\":14371,\"NEW_CALEDONIA\":13895,\"GUYANA\":13704,\"MICRONESIA\":12124,\"TRINIDAD_TOBAGO\":11861,\"BENIN\":11740,\"GAMBIA\":11692,\"UGANDA\":11505,\"NEPAL\":11287,\"SUDAN\":11210,\"TUNISIA\":10933,\"ARMENIA\":10900,\"CONGO\":10840,\"CONGO_DEMOCRATIC_REPUBLIC\":10815,\"MONGOLIA\":10196,\"FRENCH_SOUTHERN_TERRITORIES\":10032,\"DOMINICAN_REPUBLIC\":9924,\"ESTONIA\":9703,\"MARTINIQUE\":9637,\"SAINT_HELENA_ASCENSION_TRISTAN_DA_CUNHA\":9482,\"ALBANIA\":9116,\"ALGERIA\":9114,\"SURINAME\":8970,\"SERBIA\":8418,\"LUXEMBOURG\":8404,\"NIGERIA\":8372,\"SYRIA\":8242,\"MALI\":8206,\"BOTSWANA\":8151,\"NORTHERN_MARIANA_ISLANDS\":7206,\"UNITED_STATES_OUTLYING_ISLANDS\":7063,\"FRENCH_POLYNESIA\":7022,\"PARAGUAY\":6950,\"AFGHANISTAN\":6860,\"KYRGYZSTAN\":6763,\"FRENCH_GUIANA\":6708,\"R\xC9UNION\":6441,\"GABON\":6315,\"MONTENEGRO\":6182,\"BOSNIA_HERZEGOVINA\":6171,\"ZIMBABWE\":6097,\"MACEDONIA\":6056,\"SOMALIA\":5745,\"ZAMBIA\":5740,\"PAKISTAN\":5690,\"MAURITIUS\":5358,\"BRITISH_INDIAN_OCEAN_TERRITORY\":5284,\"KIRIBATI\":5224,\"JAMAICA\":5213,\"JORDAN\":4841,\"AZERBAIJAN\":4765,\"SINGAPORE\":4746,\"KOREA_SOUTH\":4737,\"C\xD4TE_DIVOIRE\":4647,\"SOUTH_GEORGIA_SANDWICH_ISLANDS\":4583,\"GRENADA\":4479,\"BELARUS\":4235,\"UZBEKISTAN\":4189,\"UNITED_ARAB_EMIRATES\":4083,\"LEBANON\":4073,\"TONGA\":3962,\"LAO\":3831,\"LIBYA\":3828,\"MYANMAR\":3675,\"COOK_ISLANDS\":3671,\"FIJI\":3490,\"HONDURAS\":3486,\"HAITI\":3436,\"LATVIA\":3346,\"MAURITANIA\":3324,\"RWANDA\":3270,\"EQUATORIAL_GUINEA\":3254,\"LIBERIA\":3189,\"FAROE_ISLANDS\":3036,\"KAZAKHSTAN\":2942,\"IRAQ\":2936,\"MOZAMBIQUE\":2832,\"GUADELOUPE\":2780,\"MALTA\":2759,\"TAJIKISTAN\":2758,\"SOLOMON_ISLANDS\":2558,\"GUINEA\":2502,\"LITHUANIA\":2435,\"CURA\xC7AO\":2410,\"TOGO\":2338,\"LIECHTENSTEIN\":2287,\"SAO_TOME_PRINCIPE\":2240,\"FALKLAND_ISLANDS\":2232,\"GUINEA_BISSAU\":2024,\"TURKMENISTAN\":1869,\"COCOS_ISLANDS\":1864,\"SIERRA_LEONE\":1836,\"BRUNEI_DARUSSALAM\":1752,\"CAMBODIA\":1594,\"ERITREA\":1577,\"BERMUDA\":1516,\"SAMOA\":1440,\"CENTRAL_AFRICAN_REPUBLIC\":1384,\"CAYMAN_ISLANDS\":1352,\"BHUTAN\":1242,\"CHAD\":1229,\"KOSOVO\":1074,\"BANGLADESH\":1045,\"NIGER\":912,\"VIRGIN_ISLANDS\":820,\"ARUBA\":793,\"PITCAIRN\":785,\"DOMINICA\":749,\"ANGUILLA\":737,\"SWAZILAND\":736,\"COMOROS\":670,\"BOUVET_ISLAND\":648,\"KOREA_NORTH\":615,\"ANDORRA\":530,\"BELIZE\":524,\"BURUNDI\":470,\"PALESTINIAN_TERRITORY\":465,\"PALAU\":405,\"GUERNSEY\":378,\"GIBRALTAR\":345,\"SAINT_LUCIA\":339,\"ANTIGUA_BARBUDA\":334,\"NORFOLK_ISLAND\":324,\"AMERICAN_SAMOA\":321,\"GUAM\":315,\"MOLDOVA\":303,\"MONTSERRAT\":300,\"SAN_MARINO\":296,\"ALAND_ISLANDS\":275,\"DJIBOUTI\":260,\"VIRGIN_ISLANDS_BRITISH\":243,\"KUWAIT\":234,\"BONAIRE_SINT_EUSTATIUS_SABA\":226,\"BARBADOS\":203,\"BAHRAIN\":201,\"TOKELAU\":199,\"MONACO\":193,\"NAURU\":192,\"SAINT_VINCENT_GRENADINES\":160,\"SOUTH_SUDAN\":160,\"LESOTHO\":146,\"SAINT_KITTS_NEVIS\":142,\"TIMOR_LESTE\":94,\"JERSEY\":72,\"CHRISTMAS_ISLAND\":65,\"WALLIS_FUTUNA\":60,\"MACAO\":58,\"SINT_MAARTEN\":54,\"MAYOTTE\":53,\"QATAR\":48,\"NIUE\":41,\"ISLE_OF_MAN\":31,\"TURKS_CAICOS_ISLANDS\":26,\"SAINT_PIERRE_MIQUELON\":18,\"SAINT_MARTIN_FRENCH\":18,\"TUVALU\":15,\"VATICAN\":12,\"INTERNATIONAL_WATERS\":9,\"SAINT_BARTH\xC9LEMY\":4}" 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '661' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '4530' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:19 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 779682837 609198027 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_name_lookup_paging.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/species/search?q=mammalia&limit=1&hl=false&verbose=false 15 | response: 16 | body: 17 | string: "{\"offset\":0,\"limit\":1,\"endOfRecords\":false,\"count\":3699,\"results\":[{\"key\":100559474,\"datasetKey\":\"16c3f9cb-4b19-4553-ac8e-ebb90003aa02\",\"scientificName\":\"Mammaliaformes\",\"canonicalName\":\"Mammaliaformes\",\"nameType\":\"SCIENTIFIC\",\"taxonomicStatus\":\"ACCEPTED\",\"origin\":\"SOURCE\",\"numDescendants\":0,\"numOccurrences\":0,\"taxonID\":\"2547897\",\"extinct\":true,\"habitats\":[],\"nomenclaturalStatus\":[],\"threatStatuses\":[],\"descriptions\":[{\"description\":\"Die 18 | Morganucodonta sind eine Gruppe ausgestorbener S\xE4ugetiervorfahren (Mammaliaformes), 19 | die in der Obertrias und im unteren Jura lebte.\"},{\"description\":\"Morganucodonta 20 | waren kleine (rund 10 bis 12 Zentimeter lange), \xE4u\xDFerlich vermutlich 21 | spitzmaus\xE4hnliche Tiere. Im Bau ihres Kiefergelenks zeigen sie \xDCbergangsmerkmale 22 | zwischen den synapsiden Vorfahren der S\xE4ugetiere und den eigentlichen S\xE4ugern, 23 | so ist noch das prim\xE4re Kiefergelenk zwischen Os articulare und Os quadratum 24 | erkennbar. Wie heutige S\xE4ugetiere hatten sie aber schon vier unterschiedliche 25 | Zahntypen: Schneidez\xE4hne, Eckz\xE4hne, Pr\xE4molaren und Molaren. Die meist 26 | drei Molaren jeder Kieferh\xE4lfte wiesen jeweils drei scharfe H\xF6cker auf, 27 | die Okklusion (der Kontakt der Z\xE4hne des Ober- und Unterkiefers) war gut 28 | ausgepr\xE4gt. Der Bau der Z\xE4hne l\xE4sst auf Insekten oder andere Kleintiere 29 | als Nahrung schlie\xDFen. Wie bei den heutigen S\xE4ugetieren und im Gegensatz 30 | zu \xE4lteren Formen wie Sinoconodon kam es zu einem einmaligen Zahnwechsel, 31 | wobei die Molaren erst beim bleibenden Gebiss erscheinen. Da dieser Zahnwechsel 32 | entwicklungsgeschichtlich mit dem S\xE4ugen in Verbindung gebracht wird, ist 33 | es denkbar, dass diese Tiere ihre Jungen s\xE4ugten. Da jedoch von einer schrittweisen 34 | Evolution der Zitzen und des damit einhergehenden Saugverhaltens der Jungtiere 35 | \xFCber einen Zustand, wie wir ihn bei den Kloakentieren (Monotremata) finden, 36 | ausgegangen wird, bei dem zun\xE4chst nur Dr\xFCsenfelder ausgebildet sind, 37 | die von den aus dem Ei geschl\xFCpften Jungtieren beleckt werden, ist im Falle 38 | des geschilderten Zahnwechsels der Morganucodonta auch die M\xF6glichkeit 39 | zu ber\xFCcksichtigen, dass der einmalige Zahnwechsel mit Molaren im bleibenden 40 | Gebiss evolutionsgeschichtlich auch in v\xF6llig anderem Zusammenhang stehen 41 | k\xF6nnte. Auch der \xFCbrige K\xF6rperbau stimmt weitgehend mit dem der S\xE4ugetiere 42 | \xFCberein, wenngleich sie im Bau der Halswirbel, des Schulterg\xFCrtels und 43 | des Beckens noch einige \xDCbergangsmerkmale aufweisen. Es d\xFCrfte sich 44 | bei ihnen um flinke, bodenbewohnende Tiere gehandelt haben.\"},{\"description\":\"Funde 45 | der Morganucodonta sind von der Obertrias bis in den unteren Jura (rund 210 46 | bis 175 Millionen Jahre) bekannt. Fossilien wurden in Europa, Asien, dem s\xFCdlichen 47 | Afrika und Nordamerika gefunden, was auf eine nahezu weltweite Verbreitung 48 | dieser Tiergruppe schlie\xDFen l\xE4sst. Sie werden zu einer Reihe von Tieren 49 | gez\xE4hlt, die fortgeschrittene s\xE4ugetier\xE4hnliche Merkmale aufweisen, 50 | sich aber in Details noch von den heutigen S\xE4ugern unterscheiden und darum 51 | als Mammaliaformes (S\xE4ugetierartige) oder als Mammalia sensu lato (im weiteren 52 | Sinn) zusammengefasst werden. Ob man sie bereits als S\xE4ugetier oder noch 53 | als S\xE4ugetiervorfahren bezeichnet, ist weitgehend Definitionsfrage. Verworfen 54 | ist hingegen eine Zugeh\xF6rigkeit zu den Triconodonta, eine S\xE4ugetiergruppe, 55 | die durch dreih\xF6ckrige Molaren charakterisiert wurde, die sich aber als 56 | keine nat\xFCrliche Gruppe herausgestellt hat.\"},{\"description\":\"Die Morganucodonta 57 | werden in zwei Familien unterteilt, die Morganucodontidae und die Megazostrodontidae. 58 | Allerdings halten manche Forscher die Megazostrodontidae f\xFCr n\xE4her mit 59 | den Docodonta als mit den Morganucodontidae verwandt. Die bekannteste Gattung 60 | der Morganucodontidae ist Morganucodon (Synonym Eozostrodon), andere Gattungen 61 | sind Erythrotherium aus S\xFCdafrika, Hallautherium (benannt nach dem Fundort 62 | Hallau) und Helvetiodon aus Europa (Schweiz), Indotherium aus Indien sowie 63 | Brachyzostrodon und Wareolestes aus Westeuropa. (Die beiden letztgenannten 64 | Gattungen werden manchmal auch den Megazostrodontidae zugerechnet.) Die Megazostrodontidae 65 | umfassen neben dem namensgebenden Megazostrodon noch Dinnetherium aus Arizona 66 | (USA) und Indozostrodon aus Indien.\"}],\"vernacularNames\":[],\"synonym\":false,\"higherClassificationMap\":{}}],\"facets\":[]}" 67 | headers: 68 | Accept-Ranges: 69 | - bytes 70 | Age: 71 | - '0' 72 | Cache-Control: 73 | - public, max-age=600 74 | Connection: 75 | - keep-alive 76 | Content-Type: 77 | - application/json 78 | Date: 79 | - Fri, 14 Nov 2025 10:26:10 GMT 80 | Expires: 81 | - '0' 82 | Pragma: 83 | - no-cache 84 | Transfer-Encoding: 85 | - chunked 86 | Vary: 87 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 88 | Via: 89 | - 1.1 varnish (Varnish/6.6) 90 | X-Content-Type-Options: 91 | - nosniff 92 | X-Frame-Options: 93 | - DENY 94 | X-Varnish: 95 | - '799932727' 96 | X-XSS-Protection: 97 | - 1; mode=block 98 | status: 99 | code: 200 100 | message: OK 101 | version: 1 102 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_get.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/142316497 15 | response: 16 | body: 17 | string: "{\"key\":142316497,\"datasetKey\":\"8575f23e-f762-11e1-a439-00145eb45e9a\",\"publishingOrgKey\":\"57254bd0-8256-11d8-b7ed-b8a03c50a862\",\"networkKeys\":[\"17abcf75-2f1e-46dd-bf75-a5b21dd02655\"],\"installationKey\":\"60454014-f762-11e1-a439-00145eb45e9a\",\"hostingOrganizationKey\":\"57254bd0-8256-11d8-b7ed-b8a03c50a862\",\"publishingCountry\":\"DE\",\"protocol\":\"BIOCASE\",\"lastCrawled\":\"2025-11-01T22:19:55.624+00:00\",\"lastParsed\":\"2025-11-05T04:17:51.395+00:00\",\"crawlId\":306,\"extensions\":{},\"basisOfRecord\":\"PRESERVED_SPECIMEN\",\"occurrenceStatus\":\"PRESENT\",\"classifications\":{\"7ddf754f-d193-4cc9-b351-99906754a03b\":{\"usage\":{\"key\":\"KHLT\",\"name\":\"Ballota 18 | acetabulosa (L.) Benth.\",\"rank\":\"SPECIES\",\"code\":\"BOTANICAL\",\"authorship\":\"(L.) 19 | Benth.\",\"genericName\":\"Ballota\",\"specificEpithet\":\"acetabulosa\",\"formattedName\":\"Ballota 20 | acetabulosa (L.) Benth.\"},\"acceptedUsage\":{\"key\":\"8X425\",\"name\":\"Pseudodictamnus 21 | acetabulosus (L.) Salmaki & Siadati\",\"rank\":\"SPECIES\",\"code\":\"BOTANICAL\",\"authorship\":\"(L.) 22 | Salmaki & Siadati\",\"genericName\":\"Pseudodictamnus\",\"specificEpithet\":\"acetabulosus\",\"formattedName\":\"Pseudodictamnus 23 | acetabulosus (L.) Salmaki & Siadati\"},\"taxonomicStatus\":\"SYNONYM\",\"classification\":[{\"key\":\"CS5HF\",\"name\":\"Eukaryota\",\"rank\":\"DOMAIN\"},{\"key\":\"P\",\"name\":\"Plantae\",\"rank\":\"KINGDOM\"},{\"key\":\"CMQ8S\",\"name\":\"Pteridobiotina\",\"rank\":\"SUBKINGDOM\"},{\"key\":\"TP\",\"name\":\"Tracheophyta\",\"rank\":\"PHYLUM\"},{\"key\":\"MG\",\"name\":\"Magnoliopsida\",\"rank\":\"CLASS\"},{\"key\":\"3F4\",\"name\":\"Lamiales\",\"rank\":\"ORDER\"},{\"key\":\"PR82T\",\"name\":\"Lamiaceae\",\"rank\":\"FAMILY\"},{\"key\":\"V5N48\",\"name\":\"Lamioideae\",\"rank\":\"SUBFAMILY\"},{\"key\":\"V594S\",\"name\":\"Marrubieae\",\"rank\":\"TRIBE\"},{\"key\":\"8WK6F\",\"name\":\"Pseudodictamnus\",\"rank\":\"GENUS\"},{\"key\":\"8X425\",\"name\":\"Pseudodictamnus 24 | acetabulosus\",\"rank\":\"SPECIES\"}],\"issues\":[]},\"d7dddbf4-2cf0-4f39-9b2a-bb099caae36c\":{\"usage\":{\"key\":\"3883382\",\"name\":\"Ballota 25 | acetabulosa (L.) Benth.\",\"rank\":\"SPECIES\",\"code\":\"BOTANICAL\",\"authorship\":\"(L.) 26 | Benth.\",\"genericName\":\"Ballota\",\"specificEpithet\":\"acetabulosa\",\"formattedName\":\"Ballota 27 | acetabulosa (L.) Benth.\"},\"acceptedUsage\":{\"key\":\"10846508\",\"name\":\"Pseudodictamnus 28 | acetabulosus (L.) Salmaki & Siadati\",\"rank\":\"SPECIES\",\"code\":\"BOTANICAL\",\"authorship\":\"(L.) 29 | Salmaki & Siadati\",\"genericName\":\"Pseudodictamnus\",\"specificEpithet\":\"acetabulosus\",\"formattedName\":\"Pseudodictamnus 30 | acetabulosus (L.) Salmaki & Siadati\"},\"taxonomicStatus\":\"SYNONYM\",\"classification\":[{\"key\":\"6\",\"name\":\"Plantae\",\"rank\":\"KINGDOM\"},{\"key\":\"7707728\",\"name\":\"Tracheophyta\",\"rank\":\"PHYLUM\"},{\"key\":\"220\",\"name\":\"Magnoliopsida\",\"rank\":\"CLASS\"},{\"key\":\"408\",\"name\":\"Lamiales\",\"rank\":\"ORDER\"},{\"key\":\"2497\",\"name\":\"Lamiaceae\",\"rank\":\"FAMILY\"},{\"key\":\"5428512\",\"name\":\"Pseudodictamnus\",\"rank\":\"GENUS\"},{\"key\":\"10846508\",\"name\":\"Pseudodictamnus 31 | acetabulosus\",\"rank\":\"SPECIES\"}],\"issues\":[]}},\"taxonKey\":3883382,\"kingdomKey\":6,\"phylumKey\":7707728,\"classKey\":220,\"orderKey\":408,\"familyKey\":2497,\"genusKey\":5428512,\"speciesKey\":10846508,\"acceptedTaxonKey\":10846508,\"scientificName\":\"Ballota 32 | acetabulosa (L.) Benth.\",\"scientificNameAuthorship\":\"(L.) Benth.\",\"acceptedScientificName\":\"Pseudodictamnus 33 | acetabulosus (L.) Salmaki & Siadati\",\"kingdom\":\"Plantae\",\"phylum\":\"Tracheophyta\",\"order\":\"Lamiales\",\"family\":\"Lamiaceae\",\"genus\":\"Pseudodictamnus\",\"species\":\"Pseudodictamnus 34 | acetabulosus\",\"genericName\":\"Ballota\",\"specificEpithet\":\"acetabulosa\",\"taxonRank\":\"SPECIES\",\"taxonomicStatus\":\"SYNONYM\",\"decimalLatitude\":38.10695,\"decimalLongitude\":26.85083,\"elevation\":0.0,\"continent\":\"ASIA\",\"gadm\":{\"level0\":{\"gid\":\"TUR\",\"name\":\"Turkey\"},\"level1\":{\"gid\":\"TUR.41_1\",\"name\":\"Izmir\"},\"level2\":{\"gid\":\"TUR.41.24_1\",\"name\":\"Seferihisar\"}},\"year\":1999,\"month\":6,\"day\":18,\"eventDate\":\"1999-06-18T00:00\",\"startDayOfYear\":169,\"endDayOfYear\":169,\"issues\":[\"GEODETIC_DATUM_ASSUMED_WGS84\",\"CONTINENT_DERIVED_FROM_COORDINATES\",\"COLLECTION_MATCH_NONE\",\"INSTITUTION_MATCH_FUZZY\"],\"lastInterpreted\":\"2025-11-05T04:17:51.395+00:00\",\"license\":\"http://creativecommons.org/licenses/by/4.0/legalcode\",\"isSequenced\":false,\"identifiers\":[{\"identifier\":\"urn:catalog:BGBM:Pontaurus:1\"}],\"media\":[],\"facts\":[],\"relations\":[],\"institutionKey\":\"4cf93dd2-06c8-4f49-89bc-a5d0805c6747\",\"isInCluster\":false,\"recordedBy\":\"Markus 35 | D\xF6ring\",\"identifiedBy\":\"Markus D\xF6ring\",\"dnaSequenceID\":[],\"geodeticDatum\":\"WGS84\",\"class\":\"Magnoliopsida\",\"countryCode\":\"TR\",\"recordedByIDs\":[],\"identifiedByIDs\":[],\"gbifRegion\":\"EUROPE\",\"country\":\"T\xFCrkiye\",\"publishedByGbifRegion\":\"EUROPE\",\"catalogNumber\":\"1\",\"institutionCode\":\"BGBM\",\"collectionCode\":\"Pontaurus\",\"gbifID\":\"142316497\"}" 36 | headers: 37 | Accept-Ranges: 38 | - bytes 39 | Age: 40 | - '660' 41 | Cache-Control: 42 | - public, max-age=3601 43 | Connection: 44 | - keep-alive 45 | Content-Length: 46 | - '4592' 47 | Content-Type: 48 | - application/json 49 | Date: 50 | - Fri, 14 Nov 2025 10:02:24 GMT 51 | Expires: 52 | - '0' 53 | Pragma: 54 | - no-cache 55 | Vary: 56 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 57 | Via: 58 | - 1.1 varnish (Varnish/6.6) 59 | X-Content-Type-Options: 60 | - nosniff 61 | X-Frame-Options: 62 | - DENY 63 | X-Varnish: 64 | - 687641447 709660265 65 | X-XSS-Protection: 66 | - '0' 67 | status: 68 | code: 200 69 | message: OK 70 | version: 1 71 | -------------------------------------------------------------------------------- /pygbif/collection/search.py: -------------------------------------------------------------------------------- 1 | from pygbif.gbifutils import gbif_baseurl, bool2str, requests_argset, gbif_GET 2 | import re 3 | 4 | def search( 5 | q=None, 6 | name=None, 7 | fuzzyName=None, 8 | preservationType=None, 9 | contentType=None, 10 | numberSpecimens=None, 11 | accessionStatus=None, 12 | personalCollection=None, 13 | sourceId=None, 14 | source=None, 15 | code=None, 16 | alternativeCode=None, 17 | contact=None, 18 | institutionKey=None, 19 | country=None, 20 | city=None, 21 | gbifRegion=None, 22 | machineTagNamespace=None, 23 | machineTagName=None, 24 | machineTagValue=None, 25 | identifier=None, 26 | identifierType=None, 27 | active=None, 28 | displayOnNHCPortal=None, 29 | masterSourceType=None, 30 | replacedBy=None, 31 | sortBy=None, 32 | sortOrder=None, 33 | offset=None, 34 | limit=None, 35 | **kwargs 36 | ): 37 | """ 38 | Search for collections in GRSciColl. 39 | 40 | :param q: [str] Simple full text search parameter. The value for this parameter can be a simple word or a phrase. Wildcards are not supported 41 | :param name: [str] Name of a GrSciColl institution or collection 42 | :param fuzzyName: [str] It searches by name fuzzily so the parameter doesn't have to be the exact name 43 | :param preservationType: [str] Preservation type of a GrSciColl collection. Accepts multiple values 44 | :param contentType: [str] Content type of a GrSciColl collection. See here for accepted values: https://techdocs.gbif.org/en/openapi/v1/registry#/Collections/listCollections 45 | :param numberSpecimens: [str] Number of specimens. It supports ranges and a '*' can be used as a wildcard 46 | :param accessionStatus: [str] Accession status of a GrSciColl collection. Available values: INSTITUTIONAL, PROJECT 47 | :param personalCollection: [bool] Flag for personal GRSciColl collections 48 | :param sourceId: [str] sourceId of MasterSourceMetadata 49 | :param source: [str] Source attribute of MasterSourceMetadata. Available values: DATASET, ORGANIZATION, IH_IRN 50 | :param code: [str] Code of a GrSciColl institution or collection 51 | :param alternativeCode: [str] Alternative code of a GrSciColl institution or collection 52 | :param contact: [str] Filters collections and institutions whose contacts contain the person key specified 53 | :param institutionKey: [str] Keys of institutions to filter by 54 | :param country: [str] Filters by country given as a ISO 639-1 (2 letter) country code 55 | :param city: [str] Filters by the city of the address. It searches in both the physical and the mailing address 56 | :param gbifRegion: [str] Filters by a gbif region. Available values: AFRICA, ASIA, EUROPE, NORTH_AMERICA, OCEANIA, LATIN_AMERICA, ANTARCTICA 57 | :param machineTagNamespace: [str] Filters for entities with a machine tag in the specified namespace 58 | :param machineTagName: [str] Filters for entities with a machine tag with the specified name (use in combination with the machineTagNamespace parameter) 59 | :param machineTagValue: [str] Filters for entities with a machine tag with the specified value (use in combination with the machineTagNamespace and machineTagName parameters) 60 | :param identifier: [str] An identifier of the type given by the identifierType parameter, for example a DOI or UUID 61 | :param identifierType: [str] An identifier type for the identifier parameter. Available values: URL, LSID, HANDLER, DOI, UUID, FTP, URI, UNKNOWN, GBIF_PORTAL, GBIF_NODE, GBIF_PARTICIPANT, GRSCICOLL_ID, GRSCICOLL_URI, IH_IRN, ROR, GRID, CITES, SYMBIOTA_UUID, WIKIDATA, NCBI_BIOCOLLECTION, ISIL, CLB_DATASET_KEY 62 | :param active: [bool] Active status of a GrSciColl institution or collection 63 | :param displayOnNHCPortal: [bool] Flag to show this record in the NHC portal 64 | :param masterSourceType: [str] The master source type of a GRSciColl institution or collection. Available values: GRSCICOLL, GBIF_REGISTRY, IH 65 | :param replacedBy: [str] Key of the entity that replaced another entity 66 | :param sortBy: [str] Field to sort the results by. It only supports the fields contained in the enum. Available values: NUMBER_SPECIMENS 67 | :param sortOrder: [str] Sort order to use with the sortBy parameter. Available values: ASC, DESC 68 | :param offset: [int] Determines the offset for the search results 69 | :param limit: [int] Controls the number of results in the page. Default 20 70 | :param kwargs: Further named arguments passed on to requests.get 71 | 72 | :return: A dictionary 73 | 74 | Usage:: 75 | from pygbif import collection as coll 76 | coll.search(query="insect") 77 | coll.search(name="Insects;Entomology", limit=2) 78 | coll.search(numberSpecimens = "0,100", limit=1) 79 | coll.search(institutionKey = "6a6ac6c5-1b8a-48db-91a2-f8661274ff80") 80 | coll.search(query = "insect", country = ["US","GB"]) 81 | """ 82 | url = gbif_baseurl + "grscicoll/collection" 83 | args = { 84 | "q": q, 85 | "name": name, 86 | "fuzzyName": fuzzyName, 87 | "preservationType": preservationType, 88 | "contentType": contentType, 89 | "numberSpecimens": numberSpecimens, 90 | "accessionStatus": accessionStatus, 91 | "personalCollection": personalCollection, 92 | "sourceId": sourceId, 93 | "source": source, 94 | "code": code, 95 | "alternativeCode": alternativeCode, 96 | "contact": contact, 97 | "institutionKey": institutionKey, 98 | "country": country, 99 | "city": city, 100 | "gbifRegion": gbifRegion, 101 | "machineTagNamespace": machineTagNamespace, 102 | "machineTagName": machineTagName, 103 | "machineTagValue": machineTagValue, 104 | "identifier": identifier, 105 | "identifierType": identifierType, 106 | "active": active, 107 | "displayOnNHCPortal": displayOnNHCPortal, 108 | "masterSourceType": masterSourceType, 109 | "replacedBy": replacedBy, 110 | "sortBy": sortBy, 111 | "sortOrder": sortOrder, 112 | "offset": offset, 113 | "limit": limit, 114 | } 115 | 116 | gbif_kwargs = {key: kwargs[key] for key in kwargs if key not in requests_argset} 117 | if gbif_kwargs is not None: 118 | xx = dict( 119 | zip([re.sub("_", ".", x) for x in gbif_kwargs.keys()], gbif_kwargs.values()) 120 | ) 121 | args.update(xx) 122 | kwargs = {key: kwargs[key] for key in kwargs if key in requests_argset} 123 | out = gbif_GET(url, args, **kwargs) 124 | return out 125 | 126 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_download_describe.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/download/describe/simpleCsv 15 | response: 16 | body: 17 | string: '{"fields":[{"name":"gbifID","type":"STRING","term":"http://rs.gbif.org/terms/1.0/gbifID","nullable":false},{"name":"datasetKey","type":"STRING","term":"http://rs.gbif.org/terms/1.0/datasetKey","nullable":false},{"name":"occurrenceID","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/occurrenceID","nullable":true},{"name":"kingdom","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/kingdom","nullable":true},{"name":"phylum","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/phylum","nullable":true},{"name":"class","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/class","nullable":true},{"name":"order","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/order","nullable":true},{"name":"family","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/family","nullable":true},{"name":"genus","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/genus","nullable":true},{"name":"species","type":"STRING","term":"http://rs.gbif.org/terms/1.0/species","nullable":true},{"name":"infraspecificEpithet","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/infraspecificEpithet","nullable":true},{"name":"taxonRank","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/taxonRank","nullable":true},{"name":"scientificName","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/scientificName","nullable":true},{"name":"verbatimScientificName","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/scientificName","nullable":true},{"name":"verbatimScientificNameAuthorship","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/scientificNameAuthorship","nullable":true},{"name":"countryCode","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/countryCode","nullable":true},{"name":"locality","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/locality","nullable":true},{"name":"stateProvince","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/stateProvince","nullable":true},{"name":"occurrenceStatus","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/occurrenceStatus","nullable":true},{"name":"individualCount","type":"INT","term":"http://rs.tdwg.org/dwc/terms/individualCount","nullable":true},{"name":"publishingOrgKey","type":"STRING","term":"http://rs.gbif.org/terms/internal/publishingOrgKey","nullable":true},{"name":"decimalLatitude","type":"DOUBLE","term":"http://rs.tdwg.org/dwc/terms/decimalLatitude","nullable":true},{"name":"decimalLongitude","type":"DOUBLE","term":"http://rs.tdwg.org/dwc/terms/decimalLongitude","nullable":true},{"name":"coordinateUncertaintyInMeters","type":"DOUBLE","term":"http://rs.tdwg.org/dwc/terms/coordinateUncertaintyInMeters","nullable":true},{"name":"coordinatePrecision","type":"DOUBLE","term":"http://rs.tdwg.org/dwc/terms/coordinatePrecision","nullable":true},{"name":"elevation","type":"DOUBLE","term":"http://rs.gbif.org/terms/1.0/elevation","nullable":true},{"name":"elevationAccuracy","type":"DOUBLE","term":"http://rs.gbif.org/terms/1.0/elevationAccuracy","nullable":true},{"name":"depth","type":"DOUBLE","term":"http://rs.gbif.org/terms/1.0/depth","nullable":true},{"name":"depthAccuracy","type":"DOUBLE","term":"http://rs.gbif.org/terms/1.0/depthAccuracy","nullable":true},{"name":"eventDate","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/eventDate","nullable":true},{"name":"day","type":"INT","term":"http://rs.tdwg.org/dwc/terms/day","nullable":true},{"name":"month","type":"INT","term":"http://rs.tdwg.org/dwc/terms/month","nullable":true},{"name":"year","type":"INT","term":"http://rs.tdwg.org/dwc/terms/year","nullable":true},{"name":"taxonKey","type":"INT","term":"http://rs.gbif.org/terms/1.0/taxonKey","nullable":true},{"name":"speciesKey","type":"INT","term":"http://rs.gbif.org/terms/1.0/speciesKey","nullable":true},{"name":"basisOfRecord","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/basisOfRecord","nullable":true},{"name":"institutionCode","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/institutionCode","nullable":true},{"name":"collectionCode","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/collectionCode","nullable":true},{"name":"catalogNumber","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/catalogNumber","nullable":true},{"name":"recordNumber","type":"STRING","term":"http://rs.tdwg.org/dwc/terms/recordNumber","nullable":true},{"name":"identifiedBy","type":"ARRAY","delimiter":";","term":"http://rs.tdwg.org/dwc/terms/identifiedBy","nullable":true},{"name":"dateIdentified","type":"DATE","typeFormat":"yyyy-MM-ddTHH:mm:ss","term":"http://rs.tdwg.org/dwc/terms/dateIdentified","nullable":true},{"name":"license","type":"STRING","term":"http://purl.org/dc/terms/license","nullable":true},{"name":"rightsHolder","type":"STRING","term":"http://purl.org/dc/terms/rightsHolder","nullable":true},{"name":"recordedBy","type":"ARRAY","delimiter":";","term":"http://rs.tdwg.org/dwc/terms/recordedBy","nullable":true},{"name":"typeStatus","type":"STRUCT,lineage: ARRAY>","term":"http://rs.tdwg.org/dwc/terms/typeStatus","nullable":true},{"name":"establishmentMeans","type":"STRUCT>","term":"http://rs.tdwg.org/dwc/terms/establishmentMeans","nullable":true},{"name":"lastInterpreted","type":"DATE","typeFormat":"yyyy-MM-ddTHH:mm:ss.SSSZ","term":"http://rs.gbif.org/terms/1.0/lastInterpreted","nullable":true},{"name":"mediaType","type":"ARRAY","delimiter":";","term":"http://rs.gbif.org/terms/1.0/mediaType","nullable":true},{"name":"issue","type":"ARRAY","delimiter":";","term":"http://rs.gbif.org/terms/1.0/issue","nullable":true}]}' 20 | headers: 21 | Accept-Ranges: 22 | - bytes 23 | Age: 24 | - '0' 25 | Cache-Control: 26 | - public, max-age=3601 27 | Connection: 28 | - keep-alive 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:13:23 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Transfer-Encoding: 38 | - chunked 39 | Vary: 40 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 41 | Via: 42 | - 1.1 varnish (Varnish/6.6) 43 | X-Content-Type-Options: 44 | - nosniff 45 | X-Frame-Options: 46 | - DENY 47 | X-Varnish: 48 | - '810779296' 49 | X-XSS-Protection: 50 | - '0' 51 | status: 52 | code: 200 53 | message: OK 54 | version: 1 55 | -------------------------------------------------------------------------------- /test/vcr_cassettes/test_count_schema.yaml: -------------------------------------------------------------------------------- 1 | interactions: 2 | - request: 3 | body: null 4 | headers: 5 | Accept: 6 | - '*/*' 7 | Accept-Encoding: 8 | - gzip, deflate 9 | Connection: 10 | - keep-alive 11 | user-agent: 12 | - python-requests/2.31.0,pygbif/0.6.6 13 | method: GET 14 | uri: https://api.gbif.org/v1/occurrence/count/schema 15 | response: 16 | body: 17 | string: '[{"dimensions":[{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"}]},{"dimensions":[{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"country","type":"org.gbif.api.vocabulary.Country"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"country","type":"org.gbif.api.vocabulary.Country"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"basisOfRecord","type":"org.gbif.api.vocabulary.BasisOfRecord"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"country","type":"org.gbif.api.vocabulary.Country"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"country","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"country","type":"org.gbif.api.vocabulary.Country"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"country","type":"org.gbif.api.vocabulary.Country"},{"key":"typeStatus","type":"org.gbif.api.vocabulary.TypeStatus"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"isGeoreferenced","type":"java.lang.Boolean"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"issue","type":"org.gbif.api.vocabulary.OccurrenceIssue"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"datasetKey","type":"java.util.UUID"},{"key":"typeStatus","type":"org.gbif.api.vocabulary.TypeStatus"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"isGeoreferenced","type":"java.lang.Boolean"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"isGeoreferenced","type":"java.lang.Boolean"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"issue","type":"org.gbif.api.vocabulary.OccurrenceIssue"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"publishingCountry","type":"org.gbif.api.vocabulary.Country"},{"key":"typeStatus","type":"org.gbif.api.vocabulary.TypeStatus"}]},{"dimensions":[{"key":"taxonKey","type":"java.lang.String"}]},{"dimensions":[{"key":"taxonKey","type":"java.lang.String"},{"key":"typeStatus","type":"org.gbif.api.vocabulary.TypeStatus"}]},{"dimensions":[{"key":"typeStatus","type":"org.gbif.api.vocabulary.TypeStatus"}]},{"dimensions":[{"key":"protocol","type":"org.gbif.api.vocabulary.EndpointType"}]},{"dimensions":[{"key":"year","type":"java.lang.Integer"}]}]' 18 | headers: 19 | Accept-Ranges: 20 | - bytes 21 | Age: 22 | - '661' 23 | Cache-Control: 24 | - public, max-age=600 25 | Connection: 26 | - keep-alive 27 | Content-Length: 28 | - '5970' 29 | Content-Type: 30 | - application/json 31 | Date: 32 | - Fri, 14 Nov 2025 10:02:19 GMT 33 | Expires: 34 | - '0' 35 | Pragma: 36 | - no-cache 37 | Vary: 38 | - Origin, Access-Control-Request-Method, Access-Control-Request-Headers 39 | Via: 40 | - 1.1 varnish (Varnish/6.6) 41 | X-Content-Type-Options: 42 | - nosniff 43 | X-Frame-Options: 44 | - DENY 45 | X-Varnish: 46 | - 729679267 638328810 47 | X-XSS-Protection: 48 | - 1; mode=block 49 | status: 50 | code: 200 51 | message: OK 52 | version: 1 53 | --------------------------------------------------------------------------------