pymetadata 0.6.2__tar.gz → 0.6.4__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.2 → pymetadata-0.6.4}/.bumpversion.toml +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/workflows/ci-cd.yml +7 -3
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/workflows/docs.yml +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/workflows/ty.yml +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/CITATION.cff +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/CLAUDE.md +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/PKG-INFO +3 -2
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/annotations.md +8 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/development.md +4 -4
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/installation.md +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/pyproject.toml +2 -1
- pymetadata-0.6.4/release-notes/0.6.3.md +16 -0
- pymetadata-0.6.4/release-notes/0.6.4.md +13 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/__init__.py +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/annotation.py +35 -20
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/omex.py +43 -24
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/unichem.py +59 -32
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/core/test_annotation.py +79 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_omex.py +63 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_unichem.py +42 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tox.ini +1 -1
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/CODEOWNERS +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/dependabot.yml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/pull_request_template.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/rulesets/apply.sh +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/rulesets/develop.json +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/rulesets/main.json +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/rulesets/tags.json +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.github/workflows/ruff.yml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.gitignore +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.pre-commit-config.yaml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.python-version +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.ruff.toml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/.zenodo.json +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/LICENSE +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/README.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/README.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/cache.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/console.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/core.annotation.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/core.creator.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/core.miriam.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/core.xref.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/index.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/log.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/omex.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/ontologies.term.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/webservices.chebi.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/webservices.ols.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/webservices.registry.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/webservices.unichem.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/api/webservices.webservice.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/HARMONY2026_slides.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/about.txt +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/android-chrome-192x192.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/android-chrome-512x512.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/apple-touch-icon.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/favicon-16x16.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/favicon-32x32.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/favicon.ico +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/favicon_io.zip +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/pymetadata-100x100-300dpi.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/images/favicon/site.webmanifest +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/index.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/omex.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/docs/robots.txt +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/annotation.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/cache_path.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/MODEL2402290004.4_pymetadata.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/create_omex.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/BioModelsMetadata_MODEL2402290004.csv +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ1.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ2.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoAcuteBZ3.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicBZ.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicRZ1.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoChronicRZ2.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/EndoNZ.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/POM253.mat +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/PlotAPCaTTaAcuteAndChronic.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/Zhou2024_Updated-model.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/getCurrentsStructureCaMKSKJrelpKinetics.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/getStartingState.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/main.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/manifest.xml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/metadata.rdf +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/modelRunnerCaMKSKJrelpKinetics1ms.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/biomodels_omex/files/model_ToRORd_LandCaMKSKJrelpKinetics.m +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/omex.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/omex_from_url.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/omex/test.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/pbpko.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/examples/sbo.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/.gitignore +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/README.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/_extension.yml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/clean.scss +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/_extensions/grantmcdermott/clean/mathjax-config.js +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/custom.scss +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_example.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_resolved.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/annotation_resolved_small.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/github_screenshot.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/headshot.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/omex_logo.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/omex_overview.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/pymetadata.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/pymetadata_large.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/images/qr_livermetabolism.png +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/mathjax-config.js +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/pymetadata.bib +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/pymetadata.qmd +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/presentations/HARMONY2026/styles.css +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.10.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.11.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.12.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.13.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.14.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.15.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.16.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.17.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.18.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.19.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.20.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.21.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.22.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.3.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.4.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.5.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.6.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.7.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.8.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.0.9.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.1.0.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.1.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.10.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.3.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.4.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.5.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.6.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.7.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.8.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.2.9.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.0.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.1.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.10.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.11.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.3.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.4.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.5.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.6.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.7.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.8.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.3.9.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.4.0.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.4.1.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.4.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.4.3.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.4.4.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.0.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.1.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.10.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.11.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.12.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.3.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.4.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.5.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.6.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.7.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.8.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.5.9.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.6.0.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.6.1.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/release-notes/0.6.2.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/scripts/llms_txt.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/cache.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/console.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/__init__.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/creator.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/miriam.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/synonym.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/core/xref.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/log.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/__init__.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/_ontology_builder.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/kisao.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/pbpko.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/sbo.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/ontologies/term.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/py.typed +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/resources/ontologies/README.md +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/__init__.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/chebi.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/ols.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/registry.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/src/pymetadata/webservices/webservice.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/core/test_creator.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/BIOMD0000000001.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/CombineArchiveShowCase.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/CombineArchiveShowCase_manifest.xml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/CompModels.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/CompModels_manifest.xml +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/data/omex/iCGB21FR.omex +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_cache.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_chebi.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_log.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_offline.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_ols.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_ontology.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_registry.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_sbo_kisao.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/tests/test_webservice.py +0 -0
- {pymetadata-0.6.2 → pymetadata-0.6.4}/zensical.toml +0 -0
|
@@ -26,7 +26,7 @@ jobs:
|
|
|
26
26
|
fail-fast: false
|
|
27
27
|
matrix:
|
|
28
28
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
29
|
-
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
29
|
+
python-version: ["3.11", "3.12", "3.13", "3.14", "3.15"]
|
|
30
30
|
# linux covers the full range, windows and macos only the newest version
|
|
31
31
|
exclude:
|
|
32
32
|
- os: windows-latest
|
|
@@ -35,12 +35,16 @@ jobs:
|
|
|
35
35
|
python-version: "3.12"
|
|
36
36
|
- os: windows-latest
|
|
37
37
|
python-version: "3.13"
|
|
38
|
+
- os: windows-latest
|
|
39
|
+
python-version: "3.14"
|
|
38
40
|
- os: macos-latest
|
|
39
41
|
python-version: "3.11"
|
|
40
42
|
- os: macos-latest
|
|
41
43
|
python-version: "3.12"
|
|
42
44
|
- os: macos-latest
|
|
43
45
|
python-version: "3.13"
|
|
46
|
+
- os: macos-latest
|
|
47
|
+
python-version: "3.14"
|
|
44
48
|
|
|
45
49
|
steps:
|
|
46
50
|
- uses: actions/checkout@v7
|
|
@@ -48,7 +52,7 @@ jobs:
|
|
|
48
52
|
persist-credentials: false
|
|
49
53
|
- name: Install uv and set the python version
|
|
50
54
|
# astral-sh/setup-uv publishes no floating major tag beyond v7
|
|
51
|
-
uses: astral-sh/setup-uv@v10.0
|
|
55
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
52
56
|
with:
|
|
53
57
|
python-version: ${{ matrix.python-version }}
|
|
54
58
|
enable-cache: true
|
|
@@ -88,7 +92,7 @@ jobs:
|
|
|
88
92
|
with:
|
|
89
93
|
persist-credentials: false
|
|
90
94
|
- name: Install uv
|
|
91
|
-
uses: astral-sh/setup-uv@v10.0
|
|
95
|
+
uses: astral-sh/setup-uv@v10.1.0
|
|
92
96
|
with:
|
|
93
97
|
python-version: "3.14"
|
|
94
98
|
enable-cache: true
|
|
@@ -8,7 +8,7 @@ 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.
|
|
11
|
+
version: "0.6.4"
|
|
12
12
|
date-released: "2026-09-07"
|
|
13
13
|
doi: "10.5281/zenodo.5308801"
|
|
14
14
|
license: MIT
|
|
@@ -22,7 +22,7 @@ uv run pre-commit install
|
|
|
22
22
|
pytest # all tests
|
|
23
23
|
pytest tests/test_omex.py # single file
|
|
24
24
|
pytest tests/test_omex.py::test_entry_from_dict # single test
|
|
25
|
-
tox r -e py3.
|
|
25
|
+
tox r -e py3.15 # single tox env (py3.11-3.15 available)
|
|
26
26
|
tox run-parallel # full matrix + ty
|
|
27
27
|
|
|
28
28
|
# lint / format / types
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pymetadata
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.4
|
|
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
|
|
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.12
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.13
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
23
24
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
24
25
|
Classifier: Topic :: Scientific/Engineering
|
|
25
26
|
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
@@ -37,7 +38,7 @@ Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
|
37
38
|
Requires-Dist: ruff>=0.16.6; extra == 'dev'
|
|
38
39
|
Requires-Dist: tox>=4.61.2; extra == 'dev'
|
|
39
40
|
Requires-Dist: ty>=0.0.78; extra == 'dev'
|
|
40
|
-
Requires-Dist: zensical>=0.0.
|
|
41
|
+
Requires-Dist: zensical>=0.0.60; extra == 'dev'
|
|
41
42
|
Provides-Extra: ontology
|
|
42
43
|
Requires-Dist: pronto>=2.7.3; extra == 'ontology'
|
|
43
44
|
Description-Content-Type: text/markdown
|
|
@@ -51,6 +51,14 @@ RDFAnnotation(qualifier=BQB.IS, resource="https://en.wikipedia.org/wiki/Cytosol"
|
|
|
51
51
|
|
|
52
52
|
`resource_normalized` returns `https://identifiers.org/<prefix>:<accession>`. For namespaces which embed their prefix in the identifier (GO, CHEBI, SBO, BTO) the prefix is already part of the term and is not added a second time.
|
|
53
53
|
|
|
54
|
+
The normalization never changes what a resource says: `resource_normalized` is a URL, and parsing it again gives the same `collection` and `term`. A collection which is not in the identifiers.org registry, or a term which does not carry the prefix of its collection, cannot be written as a compact identifier and is returned as `https://identifiers.org/<collection>/<term>`:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
annotation = RDFAnnotation(qualifier=BQB.IS, resource="urn:miriam:sabiork:1406")
|
|
58
|
+
print(annotation.resource_normalized)
|
|
59
|
+
# https://identifiers.org/sabiork/1406
|
|
60
|
+
```
|
|
61
|
+
|
|
54
62
|
## Validation
|
|
55
63
|
|
|
56
64
|
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?
|
|
@@ -27,7 +27,7 @@ A pull request can only be merged once the four required checks are green:
|
|
|
27
27
|
|
|
28
28
|
| check | workflow | content |
|
|
29
29
|
| ------- | ------------- | -------------------------------------------------------------------- |
|
|
30
|
-
| `tests` | `ci-cd.yml` | the test matrix, linux, macos and windows with python 3.11 to 3.
|
|
30
|
+
| `tests` | `ci-cd.yml` | the test matrix, linux, macos and windows with python 3.11 to 3.15 |
|
|
31
31
|
| `ruff` | `ruff.yml` | `ruff check` and `ruff format --check` |
|
|
32
32
|
| `ty` | `ty.yml` | `tox r -e ty` |
|
|
33
33
|
| `docs` | `docs.yml` | the zensical build including the api reference and the agent files |
|
|
@@ -118,16 +118,16 @@ From now on every commit is checked with ruff (lint and format) and ty, i.e., th
|
|
|
118
118
|
|
|
119
119
|
The tests are written with pytest, tox runs them against every supported python version.
|
|
120
120
|
|
|
121
|
-
The tox environments are named after the interpreter (`py3.11` to `py3.
|
|
121
|
+
The tox environments are named after the interpreter (`py3.11` to `py3.15`, see `envlist` in `tox.ini`), a single one is run with
|
|
122
122
|
```bash
|
|
123
|
-
tox r -e py3.
|
|
123
|
+
tox r -e py3.15
|
|
124
124
|
```
|
|
125
125
|
and the complete matrix, including the `ty` environment, in parallel with
|
|
126
126
|
```bash
|
|
127
127
|
tox run-parallel
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
This needs the interpreters to be available, which uv installs with `uv python install 3.11 3.12 3.13 3.14`. Continuous integration runs the same environments as `uvx --with tox-uv tox -e py3.
|
|
130
|
+
This needs the interpreters to be available, which uv installs with `uv python install 3.11 3.12 3.13 3.14 3.15`. Continuous integration runs the same environments as `uvx --with tox-uv tox -e py3.15`.
|
|
131
131
|
|
|
132
132
|
To run the tests directly against the development environment use
|
|
133
133
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Installation
|
|
2
2
|
|
|
3
|
-
`pymetadata` requires python >= 3.11 and is available from [pypi](https://pypi.python.org/pypi/pymetadata). It is pure python
|
|
3
|
+
`pymetadata` requires python >= 3.11 and is available from [pypi](https://pypi.python.org/pypi/pymetadata). It is pure python and tested with python 3.11 to 3.15 on Linux, macOS and Windows. The only compiled dependency is `pydantic-core`, which comes with [pydantic](https://docs.pydantic.dev/) and is installed from a wheel; for python 3.15 these wheels are part of pydantic 2.14, with an older pydantic it is built from source, which requires a Rust toolchain.
|
|
4
4
|
|
|
5
5
|
## With uv
|
|
6
6
|
|
|
@@ -24,6 +24,7 @@ classifiers = [
|
|
|
24
24
|
"Programming Language :: Python :: 3.12",
|
|
25
25
|
"Programming Language :: Python :: 3.13",
|
|
26
26
|
"Programming Language :: Python :: 3.14",
|
|
27
|
+
"Programming Language :: Python :: 3.15",
|
|
27
28
|
"Programming Language :: Python :: Implementation :: CPython",
|
|
28
29
|
"Topic :: Scientific/Engineering",
|
|
29
30
|
"Topic :: Scientific/Engineering :: Bio-Informatics",
|
|
@@ -57,7 +58,7 @@ dev = [
|
|
|
57
58
|
"tox>=4.61.2",
|
|
58
59
|
"pytest>=9.1.1",
|
|
59
60
|
"pytest-cov>=7.1.0",
|
|
60
|
-
"zensical>=0.0.
|
|
61
|
+
"zensical>=0.0.60",
|
|
61
62
|
"mkdocstrings-python>=2.0.8",
|
|
62
63
|
]
|
|
63
64
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Release notes for pymetadata 0.6.3
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
We are pleased to release the next version of pymetadata including the
|
|
5
|
+
following changes:
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
- support for python 3.15, which is part of the test matrix and the tox environments
|
|
9
|
+
|
|
10
|
+
## Fixes
|
|
11
|
+
- `UnichemQuery` works with the current UniChem web service again, which changed the fields of a source (`lastChecked` and `srcLastUpdated` are new, `lastUpdated`, `srcReleaseDate` and `srcReleaseNumber` were removed). Only `sourceID` and `name` of a `UnichemSource` are required now and `UnichemSource.from_dict` ignores unknown fields, so such a change, or a cache written by an older version, no longer breaks the queries
|
|
12
|
+
|
|
13
|
+
## Development
|
|
14
|
+
- the development environment requires zensical 0.0.60, the version the documentation is built with
|
|
15
|
+
|
|
16
|
+
Your pymetadata team
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Release notes for pymetadata 0.6.4
|
|
2
|
+

|
|
3
|
+
|
|
4
|
+
We are pleased to release the next version of pymetadata including the
|
|
5
|
+
following changes:
|
|
6
|
+
|
|
7
|
+
## Fixes
|
|
8
|
+
- `Omex.from_directory`, and with it `Omex.from_omex` and `Omex.from_url`, add the entries in the order of the manifest, followed by the files which the manifest does not list, sorted by their location. The entries were added in the order of `os.walk`, which is the order of the file system, so `manifest.entries` and `entries_by_format` of the same archive differed between machines
|
|
9
|
+
- `RDFAnnotation.resource_normalized` keeps the collection and the term of every resource: the result is a URL, and parsing it again gives the same `collection` and `term`. A collection which is not in the identifiers.org registry (`http://identifiers.org/sabiork/1406`, `urn:miriam:foo:bar`) was reduced to the bare term, and a term which does not match the pattern of its collection (`http://identifiers.org/chebi/000000035`) lost its collection. Both are now returned as `https://identifiers.org/<collection>/<term>`, and the compact identifier of a prefix which is not in the registry stays the URL `https://identifiers.org/CMO:0000012` (#81)
|
|
10
|
+
- identifiers.org URLs of a prefix with a hyphen or an underscore, like `ec-code`, `pdb-ccd` or `go_ref`, are parsed into collection and term instead of being kept as an arbitrary URL (#81)
|
|
11
|
+
- the collection of a MIRIAM URN and of the `collection/term` notation is lowercased, as it already was for URLs, so `TAXONOMY/9606` is the collection `taxonomy` (#81)
|
|
12
|
+
|
|
13
|
+
Your pymetadata team
|
|
@@ -48,11 +48,13 @@ def get_ols_query() -> OLSQuery:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
IDENTIFIERS_ORG_PREFIX: Final = "https://identifiers.org"
|
|
51
|
+
# prefixes of the registry consist of letters, digits, `.`, `_` and `-`,
|
|
52
|
+
# e.g., `ec-code` or `go_ref`
|
|
51
53
|
IDENTIFIERS_ORG_PATTERN_COMPACT: Final = re.compile(
|
|
52
|
-
r"^https?://identifiers.org/([a-zA-Z0-9.]+):(.+)"
|
|
54
|
+
r"^https?://identifiers.org/([a-zA-Z0-9._-]+):(.+)"
|
|
53
55
|
)
|
|
54
56
|
IDENTIFIERS_ORG_PATTERN_CLASSIC: Final = re.compile(
|
|
55
|
-
r"^https?://identifiers.org/([a-zA-Z0-9.]+)/(.+)"
|
|
57
|
+
r"^https?://identifiers.org/([a-zA-Z0-9._-]+)/(.+)"
|
|
56
58
|
)
|
|
57
59
|
|
|
58
60
|
BIOREGISTRY_PREFIX: Final = "https://bioregistry.io"
|
|
@@ -167,21 +169,15 @@ class RDFAnnotation:
|
|
|
167
169
|
match3 = MIRIAM_URN_PATTERN.match(resource)
|
|
168
170
|
if match3:
|
|
169
171
|
tokens = match3.group(1).split(":")
|
|
170
|
-
self.collection = tokens[0]
|
|
172
|
+
self.collection = tokens[0].lower()
|
|
171
173
|
self.term = ":".join(tokens[1:]).replace("%3A", ":")
|
|
172
174
|
self.provider = ProviderType.IDENTIFIERS_ORG
|
|
173
175
|
|
|
174
|
-
logger.warning(
|
|
175
|
-
"Deprecated urn pattern `%s` updated: %s",
|
|
176
|
-
resource,
|
|
177
|
-
self.resource_normalized,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
176
|
else:
|
|
181
177
|
# handle short notation
|
|
182
178
|
tokens = resource.split("/")
|
|
183
179
|
if len(tokens) > 1:
|
|
184
|
-
self.collection = tokens[0]
|
|
180
|
+
self.collection = tokens[0].lower()
|
|
185
181
|
self.term = "/".join(tokens[1:])
|
|
186
182
|
self.provider = ProviderType.IDENTIFIERS_ORG
|
|
187
183
|
elif len(tokens) == 1 and ":" in tokens[0]:
|
|
@@ -209,6 +205,13 @@ class RDFAnnotation:
|
|
|
209
205
|
if self.collection in self.replaced_collections:
|
|
210
206
|
self.collection = self.replaced_collections[self.collection]
|
|
211
207
|
|
|
208
|
+
if resource.startswith("urn:miriam:"):
|
|
209
|
+
logger.warning(
|
|
210
|
+
"Deprecated urn pattern `%s` updated: %s",
|
|
211
|
+
resource,
|
|
212
|
+
self.resource_normalized,
|
|
213
|
+
)
|
|
214
|
+
|
|
212
215
|
if validate:
|
|
213
216
|
self.validate()
|
|
214
217
|
|
|
@@ -244,21 +247,33 @@ class RDFAnnotation:
|
|
|
244
247
|
`https://identifiers.org/<prefix>:<accession>`. If the namespace is
|
|
245
248
|
embedded in the LUI the prefix is already part of the term, otherwise
|
|
246
249
|
the prefix of the identifiers.org registry is prepended.
|
|
250
|
+
|
|
251
|
+
The normalization never changes what the resource says: the result is
|
|
252
|
+
an url, and parsing it again yields the same `collection` and `term`.
|
|
253
|
+
A collection which is not in the registry, or a term which does not
|
|
254
|
+
carry the prefix of its collection, can not be written as a compact
|
|
255
|
+
identifier and is returned as
|
|
256
|
+
`https://identifiers.org/<collection>/<term>`. Whether the term is
|
|
257
|
+
valid does not matter here, this is the task of `validate`.
|
|
258
|
+
|
|
259
|
+
Resources without a collection, i.e., arbitrary urls, are returned
|
|
260
|
+
unchanged.
|
|
247
261
|
"""
|
|
248
262
|
if not self.term:
|
|
249
263
|
return None
|
|
250
264
|
|
|
251
|
-
if
|
|
252
|
-
self.
|
|
253
|
-
|
|
254
|
-
)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
265
|
+
if self.provider != ProviderType.IDENTIFIERS_ORG or self.collection is None:
|
|
266
|
+
return self.term
|
|
267
|
+
|
|
268
|
+
namespace = get_registry().ns_dict.get(self.collection, None)
|
|
269
|
+
if namespace and not namespace.namespaceEmbeddedInLui:
|
|
270
|
+
return f"{IDENTIFIERS_ORG_PREFIX}/{self.collection}:{self.term}"
|
|
271
|
+
|
|
272
|
+
# the term is only a compact identifier if its prefix is the collection
|
|
273
|
+
if self.term.lower().startswith(f"{self.collection}:"):
|
|
274
|
+
return f"{IDENTIFIERS_ORG_PREFIX}/{self.term}"
|
|
260
275
|
|
|
261
|
-
return self.term
|
|
276
|
+
return f"{IDENTIFIERS_ORG_PREFIX}/{self.collection}/{self.term}"
|
|
262
277
|
|
|
263
278
|
def __repr__(self) -> str:
|
|
264
279
|
"""Get representation string."""
|
|
@@ -743,6 +743,10 @@ class Omex:
|
|
|
743
743
|
`Omex.guess_format`; SED-ML files added this way get `master=True`,
|
|
744
744
|
as they are the entry point of a simulation study.
|
|
745
745
|
|
|
746
|
+
The entries are in the order of the manifest, followed by the files
|
|
747
|
+
which the manifest does not list, sorted by their location. The order
|
|
748
|
+
does not depend on the file system.
|
|
749
|
+
|
|
746
750
|
Args:
|
|
747
751
|
directory: directory with the content of the archive
|
|
748
752
|
|
|
@@ -785,7 +789,8 @@ class Omex:
|
|
|
785
789
|
# new archive
|
|
786
790
|
omex = Omex()
|
|
787
791
|
|
|
788
|
-
#
|
|
792
|
+
# locations of all files
|
|
793
|
+
file_paths: dict[str, str] = {}
|
|
789
794
|
for root, _dirs, files in os.walk(str(directory)):
|
|
790
795
|
for file in files:
|
|
791
796
|
file_path = os.path.join(root, file)
|
|
@@ -795,32 +800,46 @@ class Omex:
|
|
|
795
800
|
if location == "./manifest.xml":
|
|
796
801
|
# manifest is created from the internal manifest entries
|
|
797
802
|
continue
|
|
803
|
+
file_paths[location] = file_path
|
|
804
|
+
|
|
805
|
+
# The order of `os.walk` is the order of the file system, which differs
|
|
806
|
+
# between machines: the entries are added in the order of the manifest,
|
|
807
|
+
# followed by the files which the manifest does not list, by location.
|
|
808
|
+
positions: dict[str, int] = {}
|
|
809
|
+
if manifest:
|
|
810
|
+
positions = {e.location: k for k, e in enumerate(manifest.entries)}
|
|
811
|
+
locations = sorted(
|
|
812
|
+
file_paths, key=lambda loc: (positions.get(loc, len(positions)), loc)
|
|
813
|
+
)
|
|
798
814
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
master = False
|
|
813
|
-
if format and ManifestEntry.is_format(
|
|
814
|
-
format_key="sedml", format=format
|
|
815
|
-
):
|
|
816
|
-
master = True
|
|
817
|
-
entry = ManifestEntry(
|
|
818
|
-
location=location,
|
|
819
|
-
format=format,
|
|
820
|
-
master=master,
|
|
815
|
+
# iterate over all locations and add entry
|
|
816
|
+
for location in locations:
|
|
817
|
+
file_path = file_paths[location]
|
|
818
|
+
logger.debug("'%s' -> '%s'", file_path, location)
|
|
819
|
+
entry: ManifestEntry
|
|
820
|
+
if manifest and location in manifest:
|
|
821
|
+
# use entry from existing manifest
|
|
822
|
+
entry = manifest[location]
|
|
823
|
+
else:
|
|
824
|
+
if manifest and location not in manifest:
|
|
825
|
+
logger.warning(
|
|
826
|
+
"Entry with location missing in manifest.xml: '%s'",
|
|
827
|
+
location,
|
|
821
828
|
)
|
|
822
829
|
|
|
823
|
-
|
|
830
|
+
format = Omex.guess_format(Path(file_path))
|
|
831
|
+
master = False
|
|
832
|
+
if format and ManifestEntry.is_format(
|
|
833
|
+
format_key="sedml", format=format
|
|
834
|
+
):
|
|
835
|
+
master = True
|
|
836
|
+
entry = ManifestEntry(
|
|
837
|
+
location=location,
|
|
838
|
+
format=format,
|
|
839
|
+
master=master,
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
omex.add_entry(entry_path=Path(file_path), entry=entry)
|
|
824
843
|
|
|
825
844
|
return omex
|
|
826
845
|
|
|
@@ -17,9 +17,9 @@ See <https://www.ebi.ac.uk/unichem/info/webservices>.
|
|
|
17
17
|
import contextlib
|
|
18
18
|
import logging
|
|
19
19
|
import urllib.parse
|
|
20
|
-
from dataclasses import dataclass, field
|
|
20
|
+
from dataclasses import dataclass, field, fields
|
|
21
21
|
from pathlib import Path
|
|
22
|
-
from typing import ClassVar
|
|
22
|
+
from typing import Any, ClassVar
|
|
23
23
|
|
|
24
24
|
import pymetadata
|
|
25
25
|
from pymetadata.cache import (
|
|
@@ -37,34 +37,62 @@ logger = logging.getLogger(__name__)
|
|
|
37
37
|
|
|
38
38
|
@dataclass
|
|
39
39
|
class UnichemSource:
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
40
|
+
"""Database known to UniChem, see the `sources` endpoint of the web service.
|
|
41
|
+
|
|
42
|
+
Only `sourceID` and `name` are required. UniChem adds and removes the
|
|
43
|
+
descriptive fields without notice, so they are optional, and
|
|
44
|
+
`UnichemSource.from_dict` ignores fields which are not declared here.
|
|
45
|
+
|
|
46
|
+
Attributes:
|
|
47
|
+
sourceID: id of the source in UniChem
|
|
48
|
+
name: unique name of the source in UniChem, always lower case
|
|
49
|
+
nameLabel: name suitable as label of the source, in the case used by
|
|
50
|
+
the source
|
|
51
|
+
nameLong: full name of the source, as defined by the source
|
|
52
|
+
srcUrl: home page of the source
|
|
53
|
+
baseIdUrl: base url for links to a compound in the source; the
|
|
54
|
+
identifier of the compound is appended or replaces `{$id}`
|
|
55
|
+
description: description of the content of the source
|
|
56
|
+
UCICount: number of UniChem compound identifiers in the source
|
|
57
|
+
created: date the source was added to UniChem
|
|
58
|
+
lastChecked: date UniChem checked the source for an update
|
|
59
|
+
srcLastUpdated: date of the last update of the source
|
|
60
|
+
srcDetails: details on the source
|
|
61
|
+
updateComments: comments on the update of the source
|
|
62
|
+
private: the source is not public
|
|
51
63
|
"""
|
|
52
64
|
|
|
53
65
|
sourceID: int
|
|
54
|
-
srcUrl: str
|
|
55
66
|
name: str
|
|
56
|
-
nameLabel: str = field(repr=False)
|
|
57
|
-
nameLong: str = field(repr=False)
|
|
58
|
-
|
|
59
|
-
baseIdUrl: str = field(repr=False)
|
|
60
|
-
description: str = field(repr=False)
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
updateComments: str = field(repr=False)
|
|
67
|
-
private: bool = field(repr=False)
|
|
67
|
+
nameLabel: str | None = field(default=None, repr=False)
|
|
68
|
+
nameLong: str | None = field(default=None, repr=False)
|
|
69
|
+
srcUrl: str | None = None
|
|
70
|
+
baseIdUrl: str | None = field(default=None, repr=False)
|
|
71
|
+
description: str | None = field(default=None, repr=False)
|
|
72
|
+
UCICount: int | None = field(default=None, repr=False)
|
|
73
|
+
created: str | None = field(default=None, repr=False)
|
|
74
|
+
lastChecked: str | None = field(default=None, repr=False)
|
|
75
|
+
srcLastUpdated: str | None = field(default=None, repr=False)
|
|
76
|
+
srcDetails: str | None = field(default=None, repr=False)
|
|
77
|
+
updateComments: str | None = field(default=None, repr=False)
|
|
78
|
+
private: bool | None = field(default=None, repr=False)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def from_dict(cls, data: dict[str, Any]) -> "UnichemSource":
|
|
82
|
+
"""Create a source from a source of the web service or of the cache.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
data: fields of the source, fields unknown to `UnichemSource` are
|
|
86
|
+
ignored
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The source.
|
|
90
|
+
"""
|
|
91
|
+
names = {f.name for f in fields(cls)}
|
|
92
|
+
unknown = sorted(set(data) - names)
|
|
93
|
+
if unknown:
|
|
94
|
+
logger.debug("Ignored fields of UniChem source: %s", unknown)
|
|
95
|
+
return cls(**{k: v for k, v in data.items() if k in names})
|
|
68
96
|
|
|
69
97
|
|
|
70
98
|
class UnichemQuery:
|
|
@@ -117,7 +145,7 @@ class UnichemQuery:
|
|
|
117
145
|
data = read_json_cache(
|
|
118
146
|
unichem_sources_path, max_age=CACHE_DURATION_ONTOLOGY
|
|
119
147
|
)
|
|
120
|
-
return {int(k): UnichemSource(
|
|
148
|
+
return {int(k): UnichemSource.from_dict(v) for k, v in data.items()}
|
|
121
149
|
|
|
122
150
|
url = "https://www.ebi.ac.uk/unichem/api/v1/sources/"
|
|
123
151
|
try:
|
|
@@ -131,11 +159,13 @@ class UnichemQuery:
|
|
|
131
159
|
unichem_sources_path, reason=str(err)
|
|
132
160
|
)
|
|
133
161
|
if fallback is not None:
|
|
134
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
int(k): UnichemSource.from_dict(v) for k, v in fallback.items()
|
|
164
|
+
}
|
|
135
165
|
raise
|
|
136
166
|
|
|
137
167
|
sources_list: list[UnichemSource] = [
|
|
138
|
-
UnichemSource(
|
|
168
|
+
UnichemSource.from_dict(v) for v in data["sources"]
|
|
139
169
|
]
|
|
140
170
|
sources = {source.sourceID: source for source in sources_list}
|
|
141
171
|
|
|
@@ -219,9 +249,6 @@ class UnichemQuery:
|
|
|
219
249
|
accession = item["src_compound_id"]
|
|
220
250
|
if source.baseIdUrl:
|
|
221
251
|
# create and clean url
|
|
222
|
-
if not source.baseIdUrl:
|
|
223
|
-
continue
|
|
224
|
-
|
|
225
252
|
url = f"{source.baseIdUrl}{accession}"
|
|
226
253
|
|
|
227
254
|
url_accession = urllib.parse.quote(accession)
|
|
@@ -227,3 +227,82 @@ def test_resource_normalized(resource: str, expected: str) -> None:
|
|
|
227
227
|
"""Test normalization to identifiers.org compact identifiers."""
|
|
228
228
|
a = RDFAnnotation(qualifier=BQB.IS, resource=resource)
|
|
229
229
|
assert a.resource_normalized == expected
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
resource_kept_data = [
|
|
233
|
+
# collection which is not in the registry (see #81)
|
|
234
|
+
("http://identifiers.org/sabiork/1406", "https://identifiers.org/sabiork/1406"),
|
|
235
|
+
("urn:miriam:foo:bar", "https://identifiers.org/foo/bar"),
|
|
236
|
+
("foo/bar", "https://identifiers.org/foo/bar"),
|
|
237
|
+
# collection which is not in the registry, term with a prefix of its own
|
|
238
|
+
(
|
|
239
|
+
"http://identifiers.org/unit/UO:0000040",
|
|
240
|
+
"https://identifiers.org/unit/UO:0000040",
|
|
241
|
+
),
|
|
242
|
+
# compact identifier of a prefix which is not in the registry
|
|
243
|
+
("https://identifiers.org/CMO:0000012", "https://identifiers.org/CMO:0000012"),
|
|
244
|
+
("CMO:0000012", "https://identifiers.org/CMO:0000012"),
|
|
245
|
+
# term which does not match the pattern of its collection
|
|
246
|
+
(
|
|
247
|
+
"http://identifiers.org/chebi/000000035",
|
|
248
|
+
"https://identifiers.org/chebi/000000035",
|
|
249
|
+
),
|
|
250
|
+
("taxonomy/abc", "https://identifiers.org/taxonomy:abc"),
|
|
251
|
+
# prefix with a hyphen or an underscore
|
|
252
|
+
("urn:miriam:ec-code:1.1.1.1", "https://identifiers.org/ec-code:1.1.1.1"),
|
|
253
|
+
(
|
|
254
|
+
"https://identifiers.org/ec-code/1.1.1.1",
|
|
255
|
+
"https://identifiers.org/ec-code:1.1.1.1",
|
|
256
|
+
),
|
|
257
|
+
("go_ref/GO_REF:0000041", "https://identifiers.org/GO_REF:0000041"),
|
|
258
|
+
(
|
|
259
|
+
"https://identifiers.org/GO_REF:0000041",
|
|
260
|
+
"https://identifiers.org/GO_REF:0000041",
|
|
261
|
+
),
|
|
262
|
+
# prefixes are case insensitive
|
|
263
|
+
("urn:miriam:CHEBI:CHEBI%3A33699", "https://identifiers.org/CHEBI:33699"),
|
|
264
|
+
("TAXONOMY/9606", "https://identifiers.org/taxonomy:9606"),
|
|
265
|
+
]
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def assert_resource_kept(resource: str) -> None:
|
|
269
|
+
"""Assert that the normalized resource is a url with the same meaning."""
|
|
270
|
+
a = RDFAnnotation(qualifier=BQB.IS, resource=resource, validate=False)
|
|
271
|
+
normalized = a.resource_normalized
|
|
272
|
+
assert normalized
|
|
273
|
+
assert normalized.startswith("https://")
|
|
274
|
+
|
|
275
|
+
b = RDFAnnotation(qualifier=BQB.IS, resource=normalized, validate=False)
|
|
276
|
+
assert (b.collection, b.term) == (a.collection, a.term)
|
|
277
|
+
assert b.resource_normalized == normalized
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
@pytest.mark.parametrize("resource,expected", resource_kept_data)
|
|
281
|
+
def test_resource_normalized_keeps_resource(resource: str, expected: str) -> None:
|
|
282
|
+
"""Test that collection and term survive the normalization, see #81."""
|
|
283
|
+
a = RDFAnnotation(qualifier=BQB.IS, resource=resource, validate=False)
|
|
284
|
+
assert a.resource_normalized == expected
|
|
285
|
+
assert_resource_kept(resource)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@pytest.mark.parametrize(
|
|
289
|
+
"resource", [resource for resource, _ in resource_normalized_data]
|
|
290
|
+
)
|
|
291
|
+
def test_resource_normalized_roundtrip(resource: str) -> None:
|
|
292
|
+
"""Test that the normalized resource parses to the same annotation."""
|
|
293
|
+
assert_resource_kept(resource)
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_resource_normalized_registry() -> None:
|
|
297
|
+
"""Test the normalization with the sample of every registry namespace."""
|
|
298
|
+
from pymetadata.webservices.registry import get_registry
|
|
299
|
+
|
|
300
|
+
for prefix, namespace in get_registry().ns_dict.items():
|
|
301
|
+
sample_id = namespace.sampleId
|
|
302
|
+
if not sample_id:
|
|
303
|
+
continue
|
|
304
|
+
for resource in [
|
|
305
|
+
f"https://identifiers.org/{prefix}/{sample_id}",
|
|
306
|
+
f"urn:miriam:{prefix}:{sample_id.replace(':', '%3A')}",
|
|
307
|
+
]:
|
|
308
|
+
assert_resource_kept(resource)
|