pymetadata 0.6.4__tar.gz → 0.6.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.bumpversion.toml +1 -1
- {pymetadata-0.6.4 → pymetadata-0.6.6}/CITATION.cff +2 -2
- {pymetadata-0.6.4 → pymetadata-0.6.6}/PKG-INFO +5 -5
- {pymetadata-0.6.4 → pymetadata-0.6.6}/README.md +4 -4
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/annotations.md +7 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/development.md +2 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/index.md +4 -4
- {pymetadata-0.6.4 → pymetadata-0.6.6}/pyproject.toml +7 -0
- pymetadata-0.6.6/release-notes/0.6.5.md +8 -0
- pymetadata-0.6.6/release-notes/0.6.6.md +8 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/__init__.py +1 -1
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/cache.py +17 -6
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/annotation.py +28 -9
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/ols.py +17 -16
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/core/test_annotation.py +1 -1
- pymetadata-0.6.6/tests/test_annotation_resolution.py +108 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_cache.py +36 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/CODEOWNERS +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/dependabot.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/pull_request_template.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/rulesets/apply.sh +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/rulesets/develop.json +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/rulesets/main.json +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/rulesets/tags.json +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/workflows/ci-cd.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/workflows/docs.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/workflows/ruff.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.github/workflows/ty.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.gitignore +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.pre-commit-config.yaml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.python-version +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.ruff.toml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/.zenodo.json +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/CLAUDE.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/LICENSE +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/README.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/cache.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/console.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/core.annotation.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/core.creator.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/core.miriam.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/core.xref.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/index.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/log.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/omex.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/ontologies.term.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/webservices.chebi.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/webservices.ols.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/webservices.registry.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/webservices.unichem.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/api/webservices.webservice.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/HARMONY2026_slides.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/about.txt +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/android-chrome-192x192.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/android-chrome-512x512.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/apple-touch-icon.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/favicon-16x16.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/favicon-32x32.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/favicon.ico +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/favicon_io.zip +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/pymetadata-100x100-300dpi.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/images/favicon/site.webmanifest +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/installation.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/omex.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/docs/robots.txt +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/annotation.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/cache_path.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/MODEL2402290004.4_pymetadata.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/create_omex.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/BioModelsMetadata_MODEL2402290004.csv +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoAcuteBZ1.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoAcuteBZ2.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoAcuteBZ3.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoChronicBZ.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoChronicRZ1.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoChronicRZ2.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/EndoNZ.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/POM253.mat +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/PlotAPCaTTaAcuteAndChronic.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/Zhou2024_Updated-model.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/getCurrentsStructureCaMKSKJrelpKinetics.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/getStartingState.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/main.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/manifest.xml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/metadata.rdf +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/modelRunnerCaMKSKJrelpKinetics1ms.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/biomodels_omex/files/model_ToRORd_LandCaMKSKJrelpKinetics.m +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/omex.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/omex_from_url.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/omex/test.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/pbpko.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/examples/sbo.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/.gitignore +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/README.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/_extension.yml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/clean.scss +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/mathjax-config.js +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/custom.scss +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/annotation_example.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/annotation_resolved.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/annotation_resolved_small.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/github_screenshot.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/headshot.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/omex_logo.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/omex_overview.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/pymetadata.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/pymetadata_large.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/images/qr_livermetabolism.png +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/mathjax-config.js +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/pymetadata.bib +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/pymetadata.qmd +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/presentations/HARMONY2026/styles.css +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.10.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.11.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.12.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.13.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.14.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.15.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.16.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.17.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.18.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.19.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.20.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.21.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.22.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.5.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.6.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.7.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.8.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.0.9.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.1.0.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.1.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.10.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.5.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.6.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.7.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.8.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.2.9.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.0.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.1.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.10.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.11.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.5.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.6.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.7.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.8.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.3.9.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.4.0.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.4.1.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.4.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.4.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.4.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.0.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.1.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.10.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.11.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.12.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.5.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.6.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.7.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.8.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.5.9.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.6.0.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.6.1.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.6.2.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.6.3.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/release-notes/0.6.4.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/scripts/llms_txt.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/console.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/__init__.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/creator.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/miriam.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/synonym.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/core/xref.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/log.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/omex.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/__init__.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/_ontology_builder.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/kisao.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/pbpko.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/sbo.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/ontologies/term.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/py.typed +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/resources/ontologies/README.md +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/__init__.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/chebi.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/registry.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/unichem.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/src/pymetadata/webservices/webservice.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/core/test_creator.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/BIOMD0000000001.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/CombineArchiveShowCase.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/CombineArchiveShowCase_manifest.xml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/CompModels.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/CompModels_manifest.xml +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/data/omex/iCGB21FR.omex +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_chebi.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_log.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_offline.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_ols.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_omex.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_ontology.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_registry.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_sbo_kisao.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_unichem.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tests/test_webservice.py +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/tox.ini +0 -0
- {pymetadata-0.6.4 → pymetadata-0.6.6}/zensical.toml +0 -0
|
@@ -8,8 +8,8 @@ authors:
|
|
|
8
8
|
given-names: "Matthias"
|
|
9
9
|
orcid: "https://orcid.org/0000-0003-1725-179X"
|
|
10
10
|
affiliation: "Humboldt-University Berlin, Faculty of Life Science, Institute for Biology, Biology, Berlin; University Lübeck; University Hospital Schleswig-Holstein, Campus Lübeck, First Department of Medicine, Germany"
|
|
11
|
-
version: "0.6.
|
|
12
|
-
date-released: "2026-09-
|
|
11
|
+
version: "0.6.6"
|
|
12
|
+
date-released: "2026-09-22"
|
|
13
13
|
doi: "10.5281/zenodo.5308801"
|
|
14
14
|
license: MIT
|
|
15
15
|
repository-code: "https://github.com/matthiaskoenig/pymetadata"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pymetadata
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.6
|
|
4
4
|
Summary: pymetadata are python utilities for working with metadata.
|
|
5
5
|
Project-URL: Homepage, https://matthiaskoenig.github.io/pymetadata
|
|
6
6
|
Project-URL: Documentation, https://matthiaskoenig.github.io/pymetadata
|
|
@@ -64,7 +64,7 @@ If you have any questions or issues please [open an issue](https://github.com/ma
|
|
|
64
64
|
|
|
65
65
|
If you use `pymetadata` please cite the archived software on [Zenodo](https://doi.org/10.5281/zenodo.5308801):
|
|
66
66
|
|
|
67
|
-
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.
|
|
67
|
+
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.5) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.22903506
|
|
68
68
|
|
|
69
69
|
```bibtex
|
|
70
70
|
@software{konig_pymetadata,
|
|
@@ -72,10 +72,10 @@ If you use `pymetadata` please cite the archived software on [Zenodo](https://do
|
|
|
72
72
|
title = {pymetadata are python utilities for working with metadata},
|
|
73
73
|
year = {2026},
|
|
74
74
|
month = sep,
|
|
75
|
-
version = {0.6.
|
|
75
|
+
version = {0.6.5},
|
|
76
76
|
publisher = {Zenodo},
|
|
77
|
-
doi = {10.5281/zenodo.
|
|
78
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
77
|
+
doi = {10.5281/zenodo.22903506},
|
|
78
|
+
url = {https://doi.org/10.5281/zenodo.22903506},
|
|
79
79
|
}
|
|
80
80
|
```
|
|
81
81
|
|
|
@@ -19,7 +19,7 @@ If you have any questions or issues please [open an issue](https://github.com/ma
|
|
|
19
19
|
|
|
20
20
|
If you use `pymetadata` please cite the archived software on [Zenodo](https://doi.org/10.5281/zenodo.5308801):
|
|
21
21
|
|
|
22
|
-
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.
|
|
22
|
+
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.5) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.22903506
|
|
23
23
|
|
|
24
24
|
```bibtex
|
|
25
25
|
@software{konig_pymetadata,
|
|
@@ -27,10 +27,10 @@ If you use `pymetadata` please cite the archived software on [Zenodo](https://do
|
|
|
27
27
|
title = {pymetadata are python utilities for working with metadata},
|
|
28
28
|
year = {2026},
|
|
29
29
|
month = sep,
|
|
30
|
-
version = {0.6.
|
|
30
|
+
version = {0.6.5},
|
|
31
31
|
publisher = {Zenodo},
|
|
32
|
-
doi = {10.5281/zenodo.
|
|
33
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
32
|
+
doi = {10.5281/zenodo.22903506},
|
|
33
|
+
url = {https://doi.org/10.5281/zenodo.22903506},
|
|
34
34
|
}
|
|
35
35
|
```
|
|
36
36
|
|
|
@@ -59,6 +59,13 @@ print(annotation.resource_normalized)
|
|
|
59
59
|
# https://identifiers.org/sabiork/1406
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
Bioregistry URLs such as `https://bioregistry.io/CMO:0000003` also expose a
|
|
63
|
+
collection (`cmo`) and compact term (`CMO:0000003`). Their normalized resource
|
|
64
|
+
remains the original Bioregistry URL. `RDFAnnotationData` resolves configured
|
|
65
|
+
OLS ontologies even when they have no identifiers.org namespace, including
|
|
66
|
+
CMO, DRON, MMO and SCDO. Bioregistry terms are not validated against the
|
|
67
|
+
identifiers.org registry.
|
|
68
|
+
|
|
62
69
|
## Validation
|
|
63
70
|
|
|
64
71
|
Validation answers two questions: is the qualifier a real MIRIAM qualifier, and does the term match the pattern the identifiers.org registry defines for its collection?
|
|
@@ -162,6 +162,8 @@ uvx ty check
|
|
|
162
162
|
|
|
163
163
|
The configuration lives in `[tool.ty]` in `pyproject.toml`. Warnings are treated as errors, so the codebase is kept free of diagnostics. Suppress an unavoidable diagnostic with a rule specific `# ty: ignore[rule-name]` rather than a blanket comment.
|
|
164
164
|
|
|
165
|
+
`[tool.pyright]` in `pyproject.toml` is not a second type check. It only points the [pyright](https://github.com/microsoft/pyright) language server, which editors and coding agents use for navigation, at the uv environment in `.venv`, so that the imports of the dependencies resolve.
|
|
166
|
+
|
|
165
167
|
## Documentation
|
|
166
168
|
|
|
167
169
|
The documentation is built with [Zensical](https://zensical.org/), the static site generator of the Material for MkDocs authors. The sources are markdown files in `docs/`, the site is configured in `zensical.toml` in the repository root. Nothing rendered is committed: the site is built by the `documentation` workflow on every push and published to [matthiaskoenig.github.io/pymetadata](https://matthiaskoenig.github.io/pymetadata) from the `develop` branch.
|
|
@@ -70,7 +70,7 @@ If you have any questions or issues please [open an issue](https://github.com/ma
|
|
|
70
70
|
|
|
71
71
|
If you use `pymetadata` please cite the archived software on [Zenodo](https://doi.org/10.5281/zenodo.5308801):
|
|
72
72
|
|
|
73
|
-
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.
|
|
73
|
+
> König, M. (2026). *pymetadata are python utilities for working with metadata* (Version 0.6.5) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.22903506
|
|
74
74
|
|
|
75
75
|
```bibtex
|
|
76
76
|
@software{konig_pymetadata,
|
|
@@ -78,10 +78,10 @@ If you use `pymetadata` please cite the archived software on [Zenodo](https://do
|
|
|
78
78
|
title = {pymetadata are python utilities for working with metadata},
|
|
79
79
|
year = {2026},
|
|
80
80
|
month = sep,
|
|
81
|
-
version = {0.6.
|
|
81
|
+
version = {0.6.5},
|
|
82
82
|
publisher = {Zenodo},
|
|
83
|
-
doi = {10.5281/zenodo.
|
|
84
|
-
url = {https://doi.org/10.5281/zenodo.
|
|
83
|
+
doi = {10.5281/zenodo.22903506},
|
|
84
|
+
url = {https://doi.org/10.5281/zenodo.22903506},
|
|
85
85
|
}
|
|
86
86
|
```
|
|
87
87
|
|
|
@@ -93,3 +93,10 @@ include = ["src", "tests"]
|
|
|
93
93
|
[tool.ty.terminal]
|
|
94
94
|
# warnings are failures: keep the codebase free of any diagnostic
|
|
95
95
|
error-on-warning = true
|
|
96
|
+
|
|
97
|
+
[tool.pyright]
|
|
98
|
+
# pyright is not a check of the project, types are checked with ty; this only
|
|
99
|
+
# points the pyright language server of an editor or agent at the uv
|
|
100
|
+
# environment, so that the dependencies resolve
|
|
101
|
+
venvPath = "."
|
|
102
|
+
venv = ".venv"
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Release notes for pymetadata 0.6.5
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
## Fixes
|
|
5
|
+
- Bioregistry URLs now expose their collection and compact term, allowing `RDFAnnotationData` to resolve metadata through OLS while preserving the original URL. Configured OLS ontologies no longer require an identifiers.org namespace, and DRON, MMO and SCDO are now included (#87).
|
|
6
|
+
- OLS identifier prefixes are stripped once, case-insensitively, with either a colon or underscore separator. Identifiers such as `SIO_001013`, `OBI_0000070` and `chebi:37924` now produce the correct term IRI (#87).
|
|
7
|
+
|
|
8
|
+
Your pymetadata team
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Release notes for pymetadata 0.6.6
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
## Fixes
|
|
5
|
+
|
|
6
|
+
- Write web service caches atomically so concurrent Python test environments and application processes do not read partial JSON. Failed writes preserve the previous cache (#91).
|
|
7
|
+
|
|
8
|
+
Your pymetadata team
|
|
@@ -15,6 +15,7 @@ whatever was cached before.
|
|
|
15
15
|
|
|
16
16
|
import json
|
|
17
17
|
import logging
|
|
18
|
+
import tempfile
|
|
18
19
|
import time
|
|
19
20
|
from json.encoder import JSONEncoder
|
|
20
21
|
from pathlib import Path
|
|
@@ -124,7 +125,9 @@ def write_json_cache(
|
|
|
124
125
|
) -> None:
|
|
125
126
|
"""Write a JSON cache file.
|
|
126
127
|
|
|
127
|
-
Missing parent directories are created.
|
|
128
|
+
Missing parent directories are created. The file is replaced atomically so
|
|
129
|
+
concurrent readers never see partial JSON and failed writes preserve the
|
|
130
|
+
previous cache.
|
|
128
131
|
|
|
129
132
|
Args:
|
|
130
133
|
data: data to serialize
|
|
@@ -133,9 +136,17 @@ def write_json_cache(
|
|
|
133
136
|
`DataclassJSONEncoder` for dataclasses
|
|
134
137
|
"""
|
|
135
138
|
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
temporary_path: Path | None = None
|
|
140
|
+
try:
|
|
141
|
+
# Use the same filesystem for atomic replacement. Close the file before
|
|
142
|
+
# replacing it so this also works on Windows.
|
|
143
|
+
with tempfile.NamedTemporaryFile(
|
|
144
|
+
mode="w", dir=cache_path.parent, delete=False
|
|
145
|
+
) as fp:
|
|
146
|
+
temporary_path = Path(fp.name)
|
|
147
|
+
logger.info("Write cache: %s", cache_path)
|
|
139
148
|
json.dump(data, fp=fp, indent=2, cls=json_encoder)
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
temporary_path.replace(cache_path)
|
|
150
|
+
finally:
|
|
151
|
+
if temporary_path is not None:
|
|
152
|
+
temporary_path.unlink(missing_ok=True)
|
|
@@ -58,7 +58,9 @@ IDENTIFIERS_ORG_PATTERN_CLASSIC: Final = re.compile(
|
|
|
58
58
|
)
|
|
59
59
|
|
|
60
60
|
BIOREGISTRY_PREFIX: Final = "https://bioregistry.io"
|
|
61
|
-
BIOREGISTRY_PATTERN: Final = re.compile(
|
|
61
|
+
BIOREGISTRY_PATTERN: Final = re.compile(
|
|
62
|
+
r"^https?://bioregistry\.io/([a-zA-Z0-9._-]+):([^?#]+)$"
|
|
63
|
+
)
|
|
62
64
|
|
|
63
65
|
MIRIAM_URN_PATTERN: Final = re.compile(r"^urn:miriam:(.+)")
|
|
64
66
|
|
|
@@ -155,7 +157,12 @@ class RDFAnnotation:
|
|
|
155
157
|
# other urls are directly stored as resources without collection
|
|
156
158
|
self.collection = None
|
|
157
159
|
self.term = resource
|
|
158
|
-
|
|
160
|
+
match_bioregistry = BIOREGISTRY_PATTERN.match(resource)
|
|
161
|
+
if match_bioregistry:
|
|
162
|
+
self.collection = match_bioregistry.group(1).lower()
|
|
163
|
+
self.term = (
|
|
164
|
+
f"{match_bioregistry.group(1)}:{match_bioregistry.group(2)}"
|
|
165
|
+
)
|
|
159
166
|
self.provider = ProviderType.BIOREGISTRY_IO
|
|
160
167
|
else:
|
|
161
168
|
self.provider = ProviderType.NONE
|
|
@@ -196,7 +203,11 @@ class RDFAnnotation:
|
|
|
196
203
|
self.provider = ProviderType.NONE
|
|
197
204
|
|
|
198
205
|
# shorten compact terms
|
|
199
|
-
if
|
|
206
|
+
if (
|
|
207
|
+
self.term
|
|
208
|
+
and self.collection
|
|
209
|
+
and self.provider == ProviderType.IDENTIFIERS_ORG
|
|
210
|
+
):
|
|
200
211
|
self.term = self.shorten_compact_term(
|
|
201
212
|
term=self.term, collection=self.collection
|
|
202
213
|
)
|
|
@@ -256,12 +267,15 @@ class RDFAnnotation:
|
|
|
256
267
|
`https://identifiers.org/<collection>/<term>`. Whether the term is
|
|
257
268
|
valid does not matter here, this is the task of `validate`.
|
|
258
269
|
|
|
259
|
-
|
|
260
|
-
|
|
270
|
+
Bioregistry URLs retain their original resource while exposing their
|
|
271
|
+
collection and compact term for OLS resolution. Arbitrary URLs are also
|
|
272
|
+
returned unchanged.
|
|
261
273
|
"""
|
|
262
274
|
if not self.term:
|
|
263
275
|
return None
|
|
264
276
|
|
|
277
|
+
if self.provider == ProviderType.BIOREGISTRY_IO:
|
|
278
|
+
return self.resource
|
|
265
279
|
if self.provider != ProviderType.IDENTIFIERS_ORG or self.collection is None:
|
|
266
280
|
return self.term
|
|
267
281
|
|
|
@@ -353,12 +367,17 @@ class RDFAnnotation:
|
|
|
353
367
|
"""Validate qualifier and term of the annotation.
|
|
354
368
|
|
|
355
369
|
Returns:
|
|
356
|
-
True if the qualifier is a MIRIAM qualifier and
|
|
357
|
-
|
|
370
|
+
True if the qualifier is a MIRIAM qualifier and an identifiers.org
|
|
371
|
+
term matches its collection pattern. Other providers do not use
|
|
372
|
+
identifiers.org term validation.
|
|
358
373
|
"""
|
|
359
374
|
valid_qualifier: bool = self.check_qualifier(self.qualifier)
|
|
360
375
|
valid_term: bool = True
|
|
361
|
-
if
|
|
376
|
+
if (
|
|
377
|
+
self.collection
|
|
378
|
+
and self.term
|
|
379
|
+
and self.provider == ProviderType.IDENTIFIERS_ORG
|
|
380
|
+
):
|
|
362
381
|
valid_term = self.check_miriam_term()
|
|
363
382
|
|
|
364
383
|
return valid_qualifier and valid_term
|
|
@@ -408,7 +427,7 @@ class RDFAnnotationData(RDFAnnotation):
|
|
|
408
427
|
self.warnings: list = []
|
|
409
428
|
self.errors: list = []
|
|
410
429
|
|
|
411
|
-
if self.collection:
|
|
430
|
+
if self.collection and self.provider == ProviderType.IDENTIFIERS_ORG:
|
|
412
431
|
# register MIRIAM xrefs
|
|
413
432
|
namespace = get_registry().ns_dict.get(self.collection, None)
|
|
414
433
|
if not namespace:
|
|
@@ -20,6 +20,7 @@ See <https://www.ebi.ac.uk/ols4>.
|
|
|
20
20
|
|
|
21
21
|
import contextlib
|
|
22
22
|
import logging
|
|
23
|
+
import re
|
|
23
24
|
import urllib.parse
|
|
24
25
|
from dataclasses import dataclass, field
|
|
25
26
|
from pathlib import Path
|
|
@@ -70,6 +71,7 @@ ONTOLOGIES = [
|
|
|
70
71
|
OLSOntology(name="cmo"),
|
|
71
72
|
OLSOntology(name="chmo"),
|
|
72
73
|
OLSOntology(name="doid"),
|
|
74
|
+
OLSOntology(name="dron"),
|
|
73
75
|
OLSOntology(name="efo", iri_pattern="http://www.ebi.ac.uk/efo/EFO_{$Id}"),
|
|
74
76
|
OLSOntology(name="fix"),
|
|
75
77
|
OLSOntology(name="fma"),
|
|
@@ -81,12 +83,14 @@ ONTOLOGIES = [
|
|
|
81
83
|
OLSOntology(name="mondo"),
|
|
82
84
|
OLSOntology(name="ncit"),
|
|
83
85
|
OLSOntology(name="mp"),
|
|
86
|
+
OLSOntology(name="mmo"),
|
|
84
87
|
OLSOntology(name="oba"),
|
|
85
88
|
OLSOntology(name="opmi"),
|
|
86
89
|
OLSOntology(name="omit"),
|
|
87
90
|
OLSOntology(
|
|
88
91
|
name="sio", iri_pattern="http://semanticscience.org/resource/SIO_{$Id}"
|
|
89
92
|
),
|
|
93
|
+
OLSOntology(name="scdo"),
|
|
90
94
|
OLSOntology(name="vto"),
|
|
91
95
|
]
|
|
92
96
|
|
|
@@ -146,8 +150,7 @@ class OLSQuery:
|
|
|
146
150
|
"""
|
|
147
151
|
ols_ontology: OLSOntology | None = self.ontologies.get(ontology, None)
|
|
148
152
|
# remove prefix if existing
|
|
149
|
-
|
|
150
|
-
term = term.replace(f"{ontology.upper()}:", "")
|
|
153
|
+
term = re.sub(rf"^{re.escape(ontology)}[:_]", "", term, count=1, flags=re.I)
|
|
151
154
|
|
|
152
155
|
if ols_ontology is None:
|
|
153
156
|
logger.warning(
|
|
@@ -177,23 +180,21 @@ class OLSQuery:
|
|
|
177
180
|
if not term:
|
|
178
181
|
return {"errors": [], "warnings": [f"No term: '{ontology}'"]}
|
|
179
182
|
|
|
180
|
-
namespace = get_registry().ns_dict.get(ontology)
|
|
181
|
-
ols_pattern = None
|
|
182
|
-
if namespace and namespace.resources:
|
|
183
|
-
for ns_resource in namespace.resources:
|
|
184
|
-
if ns_resource.providerCode == "ols":
|
|
185
|
-
ols_pattern = ns_resource.urlPattern
|
|
186
|
-
break
|
|
187
|
-
|
|
188
|
-
if not ols_pattern:
|
|
189
|
-
return {
|
|
190
|
-
"errors": [],
|
|
191
|
-
"warnings": [f"'{ontology}' is not on OLS."],
|
|
192
|
-
}
|
|
193
|
-
|
|
194
183
|
if ontology == "taxonomy":
|
|
195
184
|
ontology = "ncbitaxon"
|
|
196
185
|
|
|
186
|
+
# Configured ontologies need no identifiers.org namespace. Bioregistry
|
|
187
|
+
# includes ontologies which are absent from that registry.
|
|
188
|
+
if ontology not in self.ontologies:
|
|
189
|
+
namespace = get_registry().ns_dict.get(ontology)
|
|
190
|
+
if not namespace or not any(
|
|
191
|
+
resource.providerCode == "ols" for resource in namespace.resources or []
|
|
192
|
+
):
|
|
193
|
+
return {
|
|
194
|
+
"errors": [],
|
|
195
|
+
"warnings": [f"'{ontology}' is not on OLS."],
|
|
196
|
+
}
|
|
197
|
+
|
|
197
198
|
iri = self.get_iri(ontology=ontology, term=term)
|
|
198
199
|
|
|
199
200
|
# double urlencode iri for OLS
|
|
@@ -149,7 +149,7 @@ rdf_annotation_data = [
|
|
|
149
149
|
(
|
|
150
150
|
BQB.IS,
|
|
151
151
|
"https://bioregistry.io/chebi:15996",
|
|
152
|
-
"RDFAnnotation(BQB.IS|
|
|
152
|
+
"RDFAnnotation(BQB.IS|chebi|chebi:15996|bioregistry.io)",
|
|
153
153
|
),
|
|
154
154
|
(
|
|
155
155
|
BQB.IS,
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Regression tests for resolver URLs and ontology identifiers."""
|
|
2
|
+
|
|
3
|
+
import urllib.parse
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from types import SimpleNamespace
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from pymetadata.core.annotation import (
|
|
10
|
+
BQB,
|
|
11
|
+
ProviderType,
|
|
12
|
+
RDFAnnotation,
|
|
13
|
+
RDFAnnotationData,
|
|
14
|
+
)
|
|
15
|
+
from pymetadata.webservices.ols import ONTOLOGIES, OLSQuery
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize("prefix", ["CMO", "cmo", "DRON", "MMO", "SCDO"])
|
|
19
|
+
def test_bioregistry_resolves_without_identifiers_namespace(
|
|
20
|
+
prefix: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
21
|
+
) -> None:
|
|
22
|
+
"""Bioregistry terms resolve through configured OLS ontologies."""
|
|
23
|
+
|
|
24
|
+
def unexpected_registry() -> None:
|
|
25
|
+
raise AssertionError(
|
|
26
|
+
"Configured Bioregistry terms need no identifiers registry"
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
monkeypatch.setattr("pymetadata.core.annotation.get_registry", unexpected_registry)
|
|
30
|
+
monkeypatch.setattr("pymetadata.webservices.ols.get_registry", unexpected_registry)
|
|
31
|
+
query = OLSQuery(ONTOLOGIES, cache_path=tmp_path)
|
|
32
|
+
monkeypatch.setattr("pymetadata.core.annotation.get_ols_query", lambda: query)
|
|
33
|
+
urls: list[str] = []
|
|
34
|
+
|
|
35
|
+
def respond(url: str) -> dict:
|
|
36
|
+
urls.append(urllib.parse.unquote(urllib.parse.unquote(url)))
|
|
37
|
+
return {"label": "body height", "description": ["Height of a body."]}
|
|
38
|
+
|
|
39
|
+
monkeypatch.setattr("pymetadata.webservices.ols.get_json", respond)
|
|
40
|
+
resource = f"https://bioregistry.io/{prefix}:0000003"
|
|
41
|
+
annotation = RDFAnnotation(BQB.IS, resource)
|
|
42
|
+
data = RDFAnnotationData(annotation)
|
|
43
|
+
|
|
44
|
+
assert annotation.collection == prefix.lower()
|
|
45
|
+
assert annotation.provider == ProviderType.BIOREGISTRY_IO
|
|
46
|
+
assert annotation.resource_normalized == resource
|
|
47
|
+
assert data.label == "body height"
|
|
48
|
+
assert data.errors == []
|
|
49
|
+
assert data.warnings == []
|
|
50
|
+
assert annotation.validate()
|
|
51
|
+
# Repeated resolution uses the successful OLS response already on disk.
|
|
52
|
+
assert RDFAnnotationData(annotation).label == "body height"
|
|
53
|
+
assert urls == [
|
|
54
|
+
f"https://www.ebi.ac.uk/ols4/api/ontologies/{prefix.lower()}/terms/"
|
|
55
|
+
f"http://purl.obolibrary.org/obo/{prefix.upper()}_0000003"
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.parametrize(
|
|
60
|
+
"ontology,term,iri",
|
|
61
|
+
[
|
|
62
|
+
("sio", "SIO_001013", "http://semanticscience.org/resource/SIO_001013"),
|
|
63
|
+
("obi", "OBI_0000070", "http://purl.obolibrary.org/obo/OBI_0000070"),
|
|
64
|
+
("chebi", "chebi:37924", "http://purl.obolibrary.org/obo/CHEBI_37924"),
|
|
65
|
+
("chebi", "CHEBI:37924", "http://purl.obolibrary.org/obo/CHEBI_37924"),
|
|
66
|
+
("sio", "001013", "http://semanticscience.org/resource/SIO_001013"),
|
|
67
|
+
],
|
|
68
|
+
)
|
|
69
|
+
def test_ols_iri_prefix(ontology: str, term: str, iri: str, tmp_path: Path) -> None:
|
|
70
|
+
"""A local identifier or CURIE contributes its ontology prefix only once."""
|
|
71
|
+
assert OLSQuery(ONTOLOGIES, cache_path=tmp_path).get_iri(ontology, term) == iri
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def test_unknown_bioregistry_ontology_is_not_guessed(
|
|
75
|
+
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
|
76
|
+
) -> None:
|
|
77
|
+
"""An unconfigured ontology without an OLS provider stays unresolved."""
|
|
78
|
+
registry = SimpleNamespace(ns_dict={})
|
|
79
|
+
monkeypatch.setattr("pymetadata.webservices.ols.get_registry", lambda: registry)
|
|
80
|
+
query = OLSQuery(ONTOLOGIES, cache_path=tmp_path)
|
|
81
|
+
monkeypatch.setattr("pymetadata.core.annotation.get_ols_query", lambda: query)
|
|
82
|
+
|
|
83
|
+
def unexpected_request(url: str) -> dict:
|
|
84
|
+
raise AssertionError(f"Unsupported ontology must not be queried: {url}")
|
|
85
|
+
|
|
86
|
+
monkeypatch.setattr("pymetadata.webservices.ols.get_json", unexpected_request)
|
|
87
|
+
data = RDFAnnotationData(
|
|
88
|
+
RDFAnnotation(BQB.IS, "https://bioregistry.io/unknown:123")
|
|
89
|
+
)
|
|
90
|
+
assert data.label is None
|
|
91
|
+
assert data.errors == []
|
|
92
|
+
assert data.warnings == ["'unknown' is not on OLS."]
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
@pytest.mark.parametrize(
|
|
96
|
+
"resource",
|
|
97
|
+
[
|
|
98
|
+
"https://bioregistryXio/CMO:0000003",
|
|
99
|
+
"https://bioregistry.io/CMO:",
|
|
100
|
+
"https://bioregistry.io/CMO",
|
|
101
|
+
],
|
|
102
|
+
)
|
|
103
|
+
def test_non_bioregistry_identifier_url_is_preserved(resource: str) -> None:
|
|
104
|
+
"""Lookalike hosts and URLs without an accession are arbitrary resources."""
|
|
105
|
+
annotation = RDFAnnotation(BQB.IS, resource)
|
|
106
|
+
assert annotation.provider == ProviderType.NONE
|
|
107
|
+
assert annotation.collection is None
|
|
108
|
+
assert annotation.resource_normalized == resource
|
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
import os
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
8
9
|
|
|
9
10
|
import pytest
|
|
10
11
|
|
|
@@ -114,3 +115,38 @@ def test_write_json_cache_creates_parents(tmp_path: Path) -> None:
|
|
|
114
115
|
write_json_cache(data={"a": 1}, cache_path=cache_path)
|
|
115
116
|
|
|
116
117
|
assert json.loads(cache_path.read_text()) == {"a": 1}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_write_json_cache_overlapping_access(tmp_path: Path) -> None:
|
|
121
|
+
"""Readers and overlapping writers never see partially written JSON."""
|
|
122
|
+
cache_path = tmp_path / "data.json"
|
|
123
|
+
write_json_cache(data={"old": True}, cache_path=cache_path)
|
|
124
|
+
|
|
125
|
+
class OverlappingEncoder(json.JSONEncoder):
|
|
126
|
+
def default(self, o: Any) -> Any:
|
|
127
|
+
"""Read and replace the cache while the outer write is in progress."""
|
|
128
|
+
assert read_json_cache(cache_path) == {"old": True}
|
|
129
|
+
write_json_cache(data={"other": "writer"}, cache_path=cache_path)
|
|
130
|
+
assert read_json_cache(cache_path) == {"other": "writer"}
|
|
131
|
+
return "encoded"
|
|
132
|
+
|
|
133
|
+
write_json_cache(
|
|
134
|
+
data={"new": object()},
|
|
135
|
+
cache_path=cache_path,
|
|
136
|
+
json_encoder=OverlappingEncoder,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
assert read_json_cache(cache_path) == {"new": "encoded"}
|
|
140
|
+
assert list(tmp_path.iterdir()) == [cache_path]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_write_json_cache_failure_preserves_cache(tmp_path: Path) -> None:
|
|
144
|
+
"""A serialization failure preserves the old cache and removes temporary files."""
|
|
145
|
+
cache_path = tmp_path / "data.json"
|
|
146
|
+
write_json_cache(data={"old": True}, cache_path=cache_path)
|
|
147
|
+
|
|
148
|
+
with pytest.raises(TypeError, match="not JSON serializable"):
|
|
149
|
+
write_json_cache(data={"new": object()}, cache_path=cache_path)
|
|
150
|
+
|
|
151
|
+
assert read_json_cache(cache_path) == {"old": True}
|
|
152
|
+
assert list(tmp_path.iterdir()) == [cache_path]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|