httk-core 2.1.0__tar.gz → 2.1.1__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.
- {httk_core-2.1.0 → httk_core-2.1.1}/PKG-INFO +1 -1
- {httk_core-2.1.0 → httk_core-2.1.1}/pyproject.toml +1 -1
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/network_policy.py +1 -1
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/__init__.py +14 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/entries.py +99 -4
- httk_core-2.1.1/src/httk/core/optimade/standard_names.py +260 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/entries.py +27 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/entries/core/__init__.py +66 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/PKG-INFO +1 -1
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/SOURCES.txt +5 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_network_policy.py +2 -2
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_optimade_resources.py +13 -6
- httk_core-2.1.1/tests/test_optimade_standard_names.py +362 -0
- httk_core-2.1.1/tests/test_optimade_timestamps.py +158 -0
- httk_core-2.1.1/tests/test_release_checker.py +176 -0
- httk_core-2.1.1/tests/test_release_prepare.py +191 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/LICENSE +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/setup.cfg +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_discover.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_json.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_plugins.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_registry_tool.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_sentinel.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/atomic_write.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/building.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/citations.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/converting.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/crypto.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/data_records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/dataset_loader.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datasets.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_bytes.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_bytes_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_common.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_file.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_file_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_filename.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_filename_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_request.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_request_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_url_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/compression.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/datastream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_common.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_file.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_file_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_filename.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_filename_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_request.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_request_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_string.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_string_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_url_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/digests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/__main__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/assets/selector.css +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/assets/selector.js +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/config.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/ecosystem.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/gitsite.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/inventories.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/lockfile.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/redirect.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/release.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/semver.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/sitetree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/sphinx_ext.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_ids.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_provider.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_types.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/exactmath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/fetching.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/files.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/identity_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/loading.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/memguard.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/_miniparser.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/optimade_filter_grammar.ebnf +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/parser.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/resources.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/install.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/installed.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/precision.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/anchor.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/export.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/sealing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/templates.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/property_definitions.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/provenance.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/py.typed +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/_base.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/io.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/schemas.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/report.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/saving.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/schema_check.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/services.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/markers.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/rational_text.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/stored_properties.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/system.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/userdirs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/validation/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/validation/iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/_nested.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/_squarefree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/fracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/leaf_codecs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/mutablefracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/numeric.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/scalar_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/surdvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_frac_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_native_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_native_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_numpy_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_numpy_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_surd_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vectormath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/coercion.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/unviewing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/unwrapping.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/cli/core/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/LICENSE +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/LICENSE.httk +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/calculations.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/files.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/records.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/references.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/runs.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/dependency_links.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/entry_points.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/requires.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/top_level.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_building.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_building_engine.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_citations.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_coercion.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_compression.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_converting.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_crypto.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_data_records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_dataset_loader.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_datasets.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_datastream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_digests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_config.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_ecosystem.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_gitsite.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_integration.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_inventories_release.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_lockfile.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_manifests_redirect.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_repair.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_semver.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_sitetree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_sphinx_ext.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_ids.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_provider.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_types.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_exactmath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_exactmath_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_examples.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_fetching.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_files.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_fracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_identity_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_leaf_codecs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_load_many.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_loading.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_memguard.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_mutablefracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_numeric.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_operator_identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_optimade_filter.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_install.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_precision.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_cli_seal.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_export.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_init_template.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_sealing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_templates.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_property_definitions.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_provenance.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_registry_discovery.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_registry_tool.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_report.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_saving.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_schema_check.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_services.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_storage_markers.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_stored_properties.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_surdvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_system_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_textstream.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_unview.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_userdirs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_validation_iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vector_family.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vector_hash_equality.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vectormath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_view_laziness.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Policy and timeout helpers for network-backed datastream opens."""
|
|
2
2
|
|
|
3
|
-
DEFAULT_NETWORK_TIMEOUT: float | None =
|
|
3
|
+
DEFAULT_NETWORK_TIMEOUT: float | None = 120.0
|
|
4
4
|
"""Default timeout for each network open; read freshly for every open, or ``None`` to disable."""
|
|
5
5
|
|
|
6
6
|
NETWORK_SCHEMES = frozenset({"http", "https", "ftp"})
|
|
@@ -11,6 +11,7 @@ from .entries import (
|
|
|
11
11
|
OptimadeReference,
|
|
12
12
|
ReferenceView,
|
|
13
13
|
decode_optimade_value,
|
|
14
|
+
decode_optional_timestamp,
|
|
14
15
|
)
|
|
15
16
|
from .filter import FilterAst, ParserError, ParserSyntaxError, parse_optimade_filter, parse_optimade_filter_raw
|
|
16
17
|
from .resources import (
|
|
@@ -24,8 +25,16 @@ from .resources import (
|
|
|
24
25
|
redact_optimade_document_text,
|
|
25
26
|
redact_optimade_url,
|
|
26
27
|
)
|
|
28
|
+
from .standard_names import (
|
|
29
|
+
STANDARD_NAME_EVIDENCE,
|
|
30
|
+
StandardSchemaCompletion,
|
|
31
|
+
complete_standard_schema,
|
|
32
|
+
infer_standard_names,
|
|
33
|
+
parse_optimade_api_version,
|
|
34
|
+
)
|
|
27
35
|
|
|
28
36
|
__all__ = [
|
|
37
|
+
"STANDARD_NAME_EVIDENCE",
|
|
29
38
|
"CalculationView",
|
|
30
39
|
"FileView",
|
|
31
40
|
"FilterAst",
|
|
@@ -41,11 +50,16 @@ __all__ = [
|
|
|
41
50
|
"ParserError",
|
|
42
51
|
"ParserSyntaxError",
|
|
43
52
|
"ReferenceView",
|
|
53
|
+
"StandardSchemaCompletion",
|
|
54
|
+
"complete_standard_schema",
|
|
44
55
|
"decode_optimade_value",
|
|
56
|
+
"decode_optional_timestamp",
|
|
57
|
+
"infer_standard_names",
|
|
45
58
|
"is_optimade_entry_url",
|
|
46
59
|
"optimade_document_root",
|
|
47
60
|
"optimade_entry_url_info",
|
|
48
61
|
"optimade_resource_from_url",
|
|
62
|
+
"parse_optimade_api_version",
|
|
49
63
|
"parse_optimade_filter",
|
|
50
64
|
"parse_optimade_filter_raw",
|
|
51
65
|
"redact_optimade_document_text",
|
|
@@ -9,6 +9,8 @@ delegated field) is requested.
|
|
|
9
9
|
"""
|
|
10
10
|
|
|
11
11
|
import datetime
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
12
14
|
from collections.abc import Callable, Mapping
|
|
13
15
|
from dataclasses import MISSING, dataclass, field, fields
|
|
14
16
|
from decimal import Decimal
|
|
@@ -120,6 +122,66 @@ def decode_optimade_value(definition: PropertyDefinition, value: object) -> obje
|
|
|
120
122
|
return _decode_by_payload(cast(Mapping[str, object], definition.as_optimade()), value)
|
|
121
123
|
|
|
122
124
|
|
|
125
|
+
# Service origins already reported for serving an offset-less optional timestamp.
|
|
126
|
+
# Bounded by the number of distinct service origins encountered in a process.
|
|
127
|
+
_naive_timestamp_origins_warned: set[str] = set()
|
|
128
|
+
|
|
129
|
+
# A full RFC 3339 local date-time: a calendar date, a ``T`` or space separator,
|
|
130
|
+
# and a time to at least the second with an optional fraction, carrying no
|
|
131
|
+
# offset. ``datetime.fromisoformat`` also accepts forms that are not RFC 3339
|
|
132
|
+
# timestamps (a bare date, a week date, an hour-minute time); only this shape is
|
|
133
|
+
# tolerated as an offset-less timestamp.
|
|
134
|
+
_RFC3339_NAIVE_DATETIME = re.compile(r"\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(?:\.\d+)?")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def decode_optional_timestamp(value: object, *, source_url: str) -> datetime.datetime | None:
|
|
138
|
+
"""Decode an optional RFC 3339 timestamp, treating an offset-less value as unknown.
|
|
139
|
+
|
|
140
|
+
A string with a full RFC 3339 date-time shape (a ``YYYY-MM-DD`` date, a ``T``
|
|
141
|
+
or space separator, and a time to at least the second with an optional
|
|
142
|
+
fraction) but no UTC offset violates RFC 3339 and has no defined meaning, so it
|
|
143
|
+
is reported once per service origin through the report channel and decodes to
|
|
144
|
+
``None`` rather than being interpreted as UTC. ``None`` decodes to ``None`` and
|
|
145
|
+
an offset-bearing value (including a ``Z`` suffix) decodes to the aware
|
|
146
|
+
timestamp. Every other value -- a non-string, an unparseable string, or a
|
|
147
|
+
partial form such as a bare date or an hour-minute time -- raises.
|
|
148
|
+
|
|
149
|
+
:param value: Raw value to decode; ``None`` decodes to ``None``.
|
|
150
|
+
:param source_url: Redacted source URL of the serving document, used to derive
|
|
151
|
+
the service origin that scopes the once-per-origin deviation warning.
|
|
152
|
+
:return: The decoded offset-aware timestamp, or ``None`` when the value is
|
|
153
|
+
absent or is a full offset-less RFC 3339 date-time.
|
|
154
|
+
:raises ValueError: If the value is not ``None`` and is not a parseable,
|
|
155
|
+
full-shape RFC 3339 timestamp string.
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
if value is None:
|
|
159
|
+
return None
|
|
160
|
+
if not isinstance(value, str):
|
|
161
|
+
raise ValueError("expected an RFC3339 timestamp string")
|
|
162
|
+
try:
|
|
163
|
+
decoded = datetime.datetime.fromisoformat(value)
|
|
164
|
+
except ValueError as exc:
|
|
165
|
+
raise ValueError(f"invalid RFC3339 timestamp {value!r}") from exc
|
|
166
|
+
if decoded.utcoffset() is not None:
|
|
167
|
+
return decoded
|
|
168
|
+
if _RFC3339_NAIVE_DATETIME.fullmatch(value) is not None:
|
|
169
|
+
split = urlsplit(source_url)
|
|
170
|
+
host = split.hostname or ""
|
|
171
|
+
if split.port is not None:
|
|
172
|
+
host = f"{host}:{split.port}"
|
|
173
|
+
origin = f"{split.scheme}://{host}" if split.scheme else source_url
|
|
174
|
+
if origin not in _naive_timestamp_origins_warned:
|
|
175
|
+
_naive_timestamp_origins_warned.add(origin)
|
|
176
|
+
logging.getLogger(__name__).warning(
|
|
177
|
+
"OPTIMADE service %s serves 'last_modified' without a UTC offset; the timestamp is treated as unknown",
|
|
178
|
+
origin,
|
|
179
|
+
extra={"context": "optimade"},
|
|
180
|
+
)
|
|
181
|
+
return None
|
|
182
|
+
raise ValueError(f"invalid RFC3339 timestamp {value!r}")
|
|
183
|
+
|
|
184
|
+
|
|
123
185
|
@dataclass(frozen=True)
|
|
124
186
|
class OptimadeEntryBackend:
|
|
125
187
|
"""Store one typed handle around an authoritative OPTIMADE resource.
|
|
@@ -159,7 +221,12 @@ class OptimadeEntryBackend:
|
|
|
159
221
|
|
|
160
222
|
@property
|
|
161
223
|
def _remote_names_by_definition_id(self) -> Mapping[str, str]:
|
|
162
|
-
"""Map semantic property IRIs to remote names from the frozen info document.
|
|
224
|
+
"""Map semantic property IRIs to remote names from the frozen info document.
|
|
225
|
+
|
|
226
|
+
Declared ``$id`` definitions are authoritative. Any remaining standard
|
|
227
|
+
namespace names that the declared specification version identifies are
|
|
228
|
+
then filled in, for IRIs the declared map does not already contain.
|
|
229
|
+
"""
|
|
163
230
|
|
|
164
231
|
root = optimade_document_root(self.resource.schema.info_document)
|
|
165
232
|
data = root.get("data")
|
|
@@ -186,6 +253,12 @@ class OptimadeEntryBackend:
|
|
|
186
253
|
f"{definition_id!r} to both {names[definition_id]!r} and {remote_name!r}"
|
|
187
254
|
)
|
|
188
255
|
names[definition_id] = remote_name
|
|
256
|
+
from .standard_names import complete_standard_schema
|
|
257
|
+
|
|
258
|
+
completion = complete_standard_schema(self.resource.schema)
|
|
259
|
+
for remote_name, inferred_id in completion.definitions_by_name.items():
|
|
260
|
+
if inferred_id not in names:
|
|
261
|
+
names[inferred_id] = remote_name
|
|
189
262
|
return MappingProxyType(names)
|
|
190
263
|
|
|
191
264
|
def value_by_definition_id(self, definition_id: str, *, default: object = _MISSING) -> object:
|
|
@@ -256,9 +329,23 @@ class OptimadeEntryBackend:
|
|
|
256
329
|
|
|
257
330
|
@stored_property
|
|
258
331
|
def last_modified(self) -> datetime.datetime | None:
|
|
259
|
-
"""Return the optional last-modified timestamp.
|
|
332
|
+
"""Return the optional last-modified timestamp.
|
|
260
333
|
|
|
261
|
-
|
|
334
|
+
A value without a UTC offset violates RFC 3339 and has no defined meaning,
|
|
335
|
+
so it is treated as unknown (decoded to ``None`` with a once-per-origin
|
|
336
|
+
deviation warning) rather than being interpreted as UTC.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
definition = self.local_schema.properties["last_modified"]
|
|
340
|
+
raw = self.value_by_definition_id(definition.definition_id)
|
|
341
|
+
if raw is _MISSING or raw is None:
|
|
342
|
+
return None
|
|
343
|
+
try:
|
|
344
|
+
return decode_optional_timestamp(raw, source_url=self.resource.document.source_url)
|
|
345
|
+
except ValueError as exc:
|
|
346
|
+
raise IncompleteOptimadeResourceError(
|
|
347
|
+
f"OPTIMADE semantic property 'last_modified' is invalid: {exc}"
|
|
348
|
+
) from exc
|
|
262
349
|
|
|
263
350
|
def _portable_value(self, name: str, expected_class: object) -> object:
|
|
264
351
|
definition = self.local_schema.properties[name]
|
|
@@ -385,7 +472,15 @@ class OptimadeEntryView:
|
|
|
385
472
|
values[record_field.name] = None
|
|
386
473
|
continue
|
|
387
474
|
try:
|
|
388
|
-
|
|
475
|
+
if record_field.name == "last_modified":
|
|
476
|
+
# An optional timestamp without a UTC offset has no defined
|
|
477
|
+
# meaning; decode it as unknown rather than assuming UTC,
|
|
478
|
+
# exactly as the backend's typed accessor does.
|
|
479
|
+
values[record_field.name] = decode_optional_timestamp(
|
|
480
|
+
raw, source_url=self._backend.resource.document.source_url
|
|
481
|
+
)
|
|
482
|
+
else:
|
|
483
|
+
values[record_field.name] = self._backend.decode_value(definition, raw)
|
|
389
484
|
except (TypeError, ValueError) as exc:
|
|
390
485
|
raise IncompleteOptimadeResourceError(
|
|
391
486
|
f"invalid semantic property {record_field.name!r}: {exc}"
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
"""Complete an OPTIMADE schema snapshot with standard-namespace name identities.
|
|
2
|
+
|
|
3
|
+
Real OPTIMADE providers almost never publish a property-definition ``$id`` in
|
|
4
|
+
their ``/info/<entry_type>`` documents, yet the specification reserves the
|
|
5
|
+
unprefixed property namespace on a standard endpoint for the standard property
|
|
6
|
+
of that name as of the specification version the service declares. This module
|
|
7
|
+
applies that namespace rule: it fills in the definition IRI of an advertised
|
|
8
|
+
unprefixed property name whose meaning the declared specification version fixes,
|
|
9
|
+
without ever overriding a declared ``$id`` and without guessing about
|
|
10
|
+
provider-prefixed names.
|
|
11
|
+
|
|
12
|
+
The pure core is :func:`infer_standard_names`; :func:`complete_standard_schema`
|
|
13
|
+
is the registry-driven wrapper that every code path building an
|
|
14
|
+
:class:`~httk.core.optimade.resources.OptimadeSchemaSnapshot` can consult.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import dataclasses
|
|
18
|
+
import re
|
|
19
|
+
from collections import Counter
|
|
20
|
+
from collections.abc import Mapping
|
|
21
|
+
from dataclasses import dataclass
|
|
22
|
+
from threading import Lock
|
|
23
|
+
from types import MappingProxyType
|
|
24
|
+
from typing import cast
|
|
25
|
+
from weakref import WeakKeyDictionary
|
|
26
|
+
|
|
27
|
+
from ..property_definitions import EntryTypeDefinition
|
|
28
|
+
from ..register.entries import OptimadeEntryBinding, known_optimade_entry_bindings, optimade_entry_binding
|
|
29
|
+
from ..register.schemas import load_entry_type_definition
|
|
30
|
+
from .entries import _is_definition_iri
|
|
31
|
+
from .resources import OptimadeDocument, OptimadeSchemaSnapshot, optimade_document_root
|
|
32
|
+
|
|
33
|
+
STANDARD_NAME_EVIDENCE = "standard-name"
|
|
34
|
+
"""Evidence tag recorded for a name inferred from the standard namespace rule."""
|
|
35
|
+
|
|
36
|
+
_VERSION = re.compile(r"^(\d+)\.(\d+)(?:\.\d+)?(?:[-+].*)?$")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_optimade_api_version(value: object) -> tuple[int, int] | None:
|
|
40
|
+
"""Return the ``(major, minor)`` of a major-1 OPTIMADE version, or ``None``.
|
|
41
|
+
|
|
42
|
+
``MAJOR.MINOR`` and ``MAJOR.MINOR.PATCH`` are accepted with optional trailing
|
|
43
|
+
pre-release or build text ignored. Only the ``(major, minor)`` pair is
|
|
44
|
+
compared. A value whose major version is not 1, or which does not parse,
|
|
45
|
+
yields ``None`` (the client supports major version 1 only).
|
|
46
|
+
|
|
47
|
+
:param value: Candidate version value.
|
|
48
|
+
:return: The ``(major, minor)`` pair, or ``None`` when unusable.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
if not isinstance(value, str):
|
|
52
|
+
return None
|
|
53
|
+
match = _VERSION.match(value)
|
|
54
|
+
if match is None:
|
|
55
|
+
return None
|
|
56
|
+
major = int(match.group(1))
|
|
57
|
+
if major != 1:
|
|
58
|
+
return None
|
|
59
|
+
return (major, int(match.group(2)))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True)
|
|
63
|
+
class StandardSchemaCompletion:
|
|
64
|
+
"""Identities inferred for standard-namespace property names.
|
|
65
|
+
|
|
66
|
+
:param entry_type: Entry type of the info document these names belong to.
|
|
67
|
+
:param api_version: Specification version the service declared, if any.
|
|
68
|
+
:param entry_type_definition_id: Definition IRI of the matched standard
|
|
69
|
+
entry type, set only when standard-name inference is possible for this
|
|
70
|
+
snapshot (a usable declared version and an owner-declared version
|
|
71
|
+
table); ``None`` otherwise.
|
|
72
|
+
:param definitions_by_name: Remote property name mapped to the property
|
|
73
|
+
definition IRI inferred for it.
|
|
74
|
+
:param evidence_by_name: Remote property name mapped to the evidence tag
|
|
75
|
+
that justified its inference.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
entry_type: str
|
|
79
|
+
api_version: str | None
|
|
80
|
+
entry_type_definition_id: str | None
|
|
81
|
+
definitions_by_name: Mapping[str, str]
|
|
82
|
+
evidence_by_name: Mapping[str, str]
|
|
83
|
+
|
|
84
|
+
def __post_init__(self) -> None:
|
|
85
|
+
object.__setattr__(self, "definitions_by_name", MappingProxyType(dict(self.definitions_by_name)))
|
|
86
|
+
object.__setattr__(self, "evidence_by_name", MappingProxyType(dict(self.evidence_by_name)))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def infer_standard_names(
|
|
90
|
+
*,
|
|
91
|
+
entry_type: str,
|
|
92
|
+
api_version: str | None,
|
|
93
|
+
advertised: Mapping[str, object],
|
|
94
|
+
definition_ids_by_name: Mapping[str, str],
|
|
95
|
+
introduced_in: Mapping[str, str],
|
|
96
|
+
) -> StandardSchemaCompletion:
|
|
97
|
+
"""Infer definition IRIs for advertised standard-namespace property names.
|
|
98
|
+
|
|
99
|
+
Pure and registry-free. For each advertised name the standard namespace
|
|
100
|
+
rule is applied: an unprefixed name that the declared specification version
|
|
101
|
+
already defines is completed to that standard property's definition IRI. A
|
|
102
|
+
declared ``$id`` always wins, provider-prefixed names carry no standard
|
|
103
|
+
semantics, and a name introduced only in a later version stays unknown. An
|
|
104
|
+
absent or non-major-1 declared version disables inference entirely.
|
|
105
|
+
|
|
106
|
+
:param entry_type: Entry type being completed.
|
|
107
|
+
:param api_version: Specification version the service declared.
|
|
108
|
+
:param advertised: The info document's ``data.properties`` mapping.
|
|
109
|
+
:param definition_ids_by_name: Standard property name to definition IRI.
|
|
110
|
+
:param introduced_in: Standard property name to its earliest spec version.
|
|
111
|
+
:return: The names inferred for this schema, possibly empty.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
declared = parse_optimade_api_version(api_version)
|
|
115
|
+
if declared is None:
|
|
116
|
+
return StandardSchemaCompletion(entry_type, api_version, None, {}, {})
|
|
117
|
+
|
|
118
|
+
declared_iris = {
|
|
119
|
+
cast(str, doc.get("$id"))
|
|
120
|
+
for doc in advertised.values()
|
|
121
|
+
if isinstance(doc, Mapping) and _is_definition_iri(doc.get("$id"))
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
inferred: dict[str, str] = {}
|
|
125
|
+
for name, document in advertised.items():
|
|
126
|
+
if not isinstance(name, str):
|
|
127
|
+
continue
|
|
128
|
+
if isinstance(document, Mapping) and _is_definition_iri(document.get("$id")):
|
|
129
|
+
# A declared identity is authoritative; never infer over it.
|
|
130
|
+
continue
|
|
131
|
+
if name.startswith("_"):
|
|
132
|
+
# Provider-prefixed names, and any other underscore-led name outside
|
|
133
|
+
# the well-formed prefix shape, are outside the standard namespace.
|
|
134
|
+
continue
|
|
135
|
+
iri = definition_ids_by_name.get(name)
|
|
136
|
+
if iri is None:
|
|
137
|
+
continue
|
|
138
|
+
introduced = parse_optimade_api_version(introduced_in.get(name))
|
|
139
|
+
if introduced is None or introduced > declared:
|
|
140
|
+
continue
|
|
141
|
+
inferred[name] = iri
|
|
142
|
+
|
|
143
|
+
# Declared identities win, and one IRI must never be claimed by two names.
|
|
144
|
+
inferred = {name: iri for name, iri in inferred.items() if iri not in declared_iris}
|
|
145
|
+
iri_counts = Counter(inferred.values())
|
|
146
|
+
inferred = {name: iri for name, iri in inferred.items() if iri_counts[iri] == 1}
|
|
147
|
+
|
|
148
|
+
evidence = {name: STANDARD_NAME_EVIDENCE for name in inferred}
|
|
149
|
+
return StandardSchemaCompletion(entry_type, api_version, None, inferred, evidence)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
_completion_cache: WeakKeyDictionary[OptimadeSchemaSnapshot, tuple[tuple[int, ...], StandardSchemaCompletion]] = (
|
|
153
|
+
WeakKeyDictionary()
|
|
154
|
+
)
|
|
155
|
+
_completion_cache_lock = Lock()
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _registry_fingerprint() -> tuple[int, ...]:
|
|
159
|
+
# Identity of each currently registered binding. A binding re-registered
|
|
160
|
+
# under the same IRI is a different object, so this changes and invalidates
|
|
161
|
+
# a cached completion that resolved against the old table.
|
|
162
|
+
return tuple(id(optimade_entry_binding(definition_id)) for definition_id in known_optimade_entry_bindings())
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _resolve_binding(entry_type: str) -> tuple[OptimadeEntryBinding, EntryTypeDefinition] | None:
|
|
166
|
+
matches: list[tuple[OptimadeEntryBinding, EntryTypeDefinition]] = []
|
|
167
|
+
for definition_id in known_optimade_entry_bindings():
|
|
168
|
+
binding = optimade_entry_binding(definition_id)
|
|
169
|
+
if binding is None:
|
|
170
|
+
continue
|
|
171
|
+
try:
|
|
172
|
+
definition = load_entry_type_definition(binding.definition_id)
|
|
173
|
+
except ValueError:
|
|
174
|
+
# A binding whose entry-type definition is not vendored here simply
|
|
175
|
+
# cannot be matched by name; skip it without failing the read.
|
|
176
|
+
continue
|
|
177
|
+
if definition.name == entry_type:
|
|
178
|
+
matches.append((binding, definition))
|
|
179
|
+
if len(matches) != 1:
|
|
180
|
+
return None
|
|
181
|
+
return matches[0]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def _info_api_version(document: OptimadeDocument) -> str | None:
|
|
185
|
+
try:
|
|
186
|
+
root = optimade_document_root(document)
|
|
187
|
+
except ValueError:
|
|
188
|
+
return None
|
|
189
|
+
meta = root.get("meta")
|
|
190
|
+
if not isinstance(meta, Mapping):
|
|
191
|
+
return None
|
|
192
|
+
version = meta.get("api_version")
|
|
193
|
+
return version if isinstance(version, str) else None
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _info_properties(document: OptimadeDocument) -> Mapping[str, object]:
|
|
197
|
+
try:
|
|
198
|
+
root = optimade_document_root(document)
|
|
199
|
+
except ValueError:
|
|
200
|
+
return {}
|
|
201
|
+
data = root.get("data")
|
|
202
|
+
if not isinstance(data, Mapping):
|
|
203
|
+
return {}
|
|
204
|
+
properties = data.get("properties")
|
|
205
|
+
if not isinstance(properties, Mapping):
|
|
206
|
+
return {}
|
|
207
|
+
return properties
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _complete_standard_schema(snapshot: OptimadeSchemaSnapshot) -> StandardSchemaCompletion:
|
|
211
|
+
api_version = _info_api_version(snapshot.info_document)
|
|
212
|
+
resolved = _resolve_binding(snapshot.entry_type)
|
|
213
|
+
# ``entry_type_definition_id`` is set only when name-based inference is
|
|
214
|
+
# actually possible for this snapshot: a usable (major-1) declared version
|
|
215
|
+
# and a non-empty owner-declared version table. Otherwise it stays None, so
|
|
216
|
+
# a client never binds a typed backend that could identify nothing.
|
|
217
|
+
if resolved is None or parse_optimade_api_version(api_version) is None:
|
|
218
|
+
return StandardSchemaCompletion(snapshot.entry_type, api_version, None, {}, {})
|
|
219
|
+
binding, definition = resolved
|
|
220
|
+
versions = binding.standard_property_versions
|
|
221
|
+
if not versions:
|
|
222
|
+
return StandardSchemaCompletion(snapshot.entry_type, api_version, None, {}, {})
|
|
223
|
+
definition_ids_by_name = {name: prop.definition_id for name, prop in definition.properties.items()}
|
|
224
|
+
completion = infer_standard_names(
|
|
225
|
+
entry_type=snapshot.entry_type,
|
|
226
|
+
api_version=api_version,
|
|
227
|
+
advertised=_info_properties(snapshot.info_document),
|
|
228
|
+
definition_ids_by_name=definition_ids_by_name,
|
|
229
|
+
introduced_in=versions,
|
|
230
|
+
)
|
|
231
|
+
return dataclasses.replace(completion, entry_type_definition_id=definition.definition_id)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def complete_standard_schema(snapshot: OptimadeSchemaSnapshot) -> StandardSchemaCompletion:
|
|
235
|
+
"""Return the standard-name completion for one schema snapshot.
|
|
236
|
+
|
|
237
|
+
Registry-driven wrapper over :func:`infer_standard_names`. It reads the
|
|
238
|
+
declared specification version from the info document's own
|
|
239
|
+
``meta.api_version``, resolves the standard entry type by name against the
|
|
240
|
+
registered OPTIMADE entry bindings, and gates name-based inference on that
|
|
241
|
+
binding's ``standard_property_versions`` table. It never raises for
|
|
242
|
+
malformed or hostile remote input: the safe answer is an empty completion.
|
|
243
|
+
``entry_type_definition_id`` is set only when standard-name inference is
|
|
244
|
+
possible for this snapshot (a usable declared version and an owner-declared
|
|
245
|
+
version table).
|
|
246
|
+
|
|
247
|
+
:param snapshot: Schema snapshot whose advertised names to complete.
|
|
248
|
+
:return: The inferred completion, empty when unresolved or unsupported by
|
|
249
|
+
the declared version.
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
fingerprint = _registry_fingerprint()
|
|
253
|
+
with _completion_cache_lock:
|
|
254
|
+
cached = _completion_cache.get(snapshot)
|
|
255
|
+
if cached is not None and cached[0] == fingerprint:
|
|
256
|
+
return cached[1]
|
|
257
|
+
result = _complete_standard_schema(snapshot)
|
|
258
|
+
with _completion_cache_lock:
|
|
259
|
+
_completion_cache[snapshot] = (fingerprint, result)
|
|
260
|
+
return result
|
|
@@ -206,6 +206,12 @@ class OptimadeEntryBinding:
|
|
|
206
206
|
:param view: The lazy view class reference.
|
|
207
207
|
:param property_decoders: Property definition IRIs mapped to lazy decoder references.
|
|
208
208
|
:param query_fields: Property definition IRIs supported for querying, if restricted.
|
|
209
|
+
:param standard_property_versions: Standard property names of this entry type
|
|
210
|
+
mapped to the earliest OPTIMADE specification version in which each name
|
|
211
|
+
is a standard property of the type. Declared by the entry-type owner, it
|
|
212
|
+
is the table that gates name-based inference of unprefixed properties
|
|
213
|
+
that carry no ``$id`` (see
|
|
214
|
+
:func:`~httk.core.optimade.standard_names.complete_standard_schema`).
|
|
209
215
|
"""
|
|
210
216
|
|
|
211
217
|
name: str
|
|
@@ -214,6 +220,7 @@ class OptimadeEntryBinding:
|
|
|
214
220
|
view: str
|
|
215
221
|
property_decoders: Mapping[str, str] = field(default_factory=dict)
|
|
216
222
|
query_fields: tuple[str, ...] | None = None
|
|
223
|
+
standard_property_versions: Mapping[str, str] = field(default_factory=dict)
|
|
217
224
|
|
|
218
225
|
def __post_init__(self) -> None:
|
|
219
226
|
_validate_nonempty_optimade_string(self.name, label="binding name")
|
|
@@ -236,7 +243,20 @@ class OptimadeEntryBinding:
|
|
|
236
243
|
if definition_id in seen:
|
|
237
244
|
raise ValueError(f"query field is listed more than once: {definition_id!r}")
|
|
238
245
|
seen.add(definition_id)
|
|
246
|
+
if not isinstance(self.standard_property_versions, Mapping):
|
|
247
|
+
raise TypeError("standard_property_versions must be a mapping of property names to spec versions")
|
|
248
|
+
from httk.core.optimade.standard_names import parse_optimade_api_version
|
|
249
|
+
|
|
250
|
+
versions: dict[str, str] = {}
|
|
251
|
+
for property_name, version in self.standard_property_versions.items():
|
|
252
|
+
_validate_nonempty_optimade_string(property_name, label="standard property name")
|
|
253
|
+
if not isinstance(version, str) or parse_optimade_api_version(version) is None:
|
|
254
|
+
raise ValueError(
|
|
255
|
+
f"standard property version for {property_name!r} must be a major-1 OPTIMADE version string"
|
|
256
|
+
)
|
|
257
|
+
versions[property_name] = version
|
|
239
258
|
object.__setattr__(self, "property_decoders", MappingProxyType(decoders))
|
|
259
|
+
object.__setattr__(self, "standard_property_versions", MappingProxyType(versions))
|
|
240
260
|
|
|
241
261
|
def resolve_backend(self) -> type:
|
|
242
262
|
"""Import and return this binding's backend class on demand.
|
|
@@ -287,6 +307,7 @@ def register_optimade_entry_binding(
|
|
|
287
307
|
view: str,
|
|
288
308
|
property_decoders: Mapping[str, str] | None = None,
|
|
289
309
|
query_fields: tuple[str, ...] | None = None,
|
|
310
|
+
standard_property_versions: Mapping[str, str] | None = None,
|
|
290
311
|
) -> None:
|
|
291
312
|
"""Register one lazy typed binding, selected only by exact definition IRI.
|
|
292
313
|
|
|
@@ -296,6 +317,11 @@ def register_optimade_entry_binding(
|
|
|
296
317
|
:param view: The lazy view class reference.
|
|
297
318
|
:param property_decoders: Property definition IRIs mapped to lazy decoder references.
|
|
298
319
|
:param query_fields: Property definition IRIs supported for querying, if restricted.
|
|
320
|
+
:param standard_property_versions: Standard property names of this entry type
|
|
321
|
+
mapped to the earliest OPTIMADE specification version in which each name
|
|
322
|
+
is a standard property of the type. This table gates name-based
|
|
323
|
+
inference of unprefixed properties that a service advertises without a
|
|
324
|
+
``$id`` definition.
|
|
299
325
|
:raises ValueError: If the definition IRI is already registered or input is invalid.
|
|
300
326
|
"""
|
|
301
327
|
|
|
@@ -306,6 +332,7 @@ def register_optimade_entry_binding(
|
|
|
306
332
|
view=view,
|
|
307
333
|
property_decoders={} if property_decoders is None else property_decoders,
|
|
308
334
|
query_fields=query_fields,
|
|
335
|
+
standard_property_versions={} if standard_property_versions is None else standard_property_versions,
|
|
309
336
|
)
|
|
310
337
|
if definition_id in _optimade_entry_bindings:
|
|
311
338
|
raise ValueError(f"OPTIMADE entry binding is already registered: {definition_id!r}")
|
|
@@ -36,12 +36,76 @@ register_entry_record(
|
|
|
36
36
|
definition_id="https://schemas.optimade.org/defs/v1.2/entrytypes/optimade/references",
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
# Earliest OPTIMADE specification version in which each standard property name
|
|
40
|
+
# is a property of this entry type (determined from the specification text at
|
|
41
|
+
# release tags v1.0.1, v1.1.0, v1.2.0, v1.3.0). Gates standard-name inference
|
|
42
|
+
# for services that publish no property-definition $id.
|
|
43
|
+
_REFERENCES_INTRODUCED_IN = {
|
|
44
|
+
name: "1.0"
|
|
45
|
+
for name in (
|
|
46
|
+
"address",
|
|
47
|
+
"annote",
|
|
48
|
+
"authors",
|
|
49
|
+
"bib_type",
|
|
50
|
+
"booktitle",
|
|
51
|
+
"chapter",
|
|
52
|
+
"crossref",
|
|
53
|
+
"doi",
|
|
54
|
+
"edition",
|
|
55
|
+
"editors",
|
|
56
|
+
"howpublished",
|
|
57
|
+
"id",
|
|
58
|
+
"immutable_id",
|
|
59
|
+
"institution",
|
|
60
|
+
"journal",
|
|
61
|
+
"key",
|
|
62
|
+
"last_modified",
|
|
63
|
+
"month",
|
|
64
|
+
"note",
|
|
65
|
+
"number",
|
|
66
|
+
"organization",
|
|
67
|
+
"pages",
|
|
68
|
+
"publisher",
|
|
69
|
+
"school",
|
|
70
|
+
"series",
|
|
71
|
+
"title",
|
|
72
|
+
"type",
|
|
73
|
+
"url",
|
|
74
|
+
"volume",
|
|
75
|
+
"year",
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
# The files entry type itself first appears in OPTIMADE 1.2.
|
|
79
|
+
_FILES_INTRODUCED_IN = {
|
|
80
|
+
name: "1.2"
|
|
81
|
+
for name in (
|
|
82
|
+
"atime",
|
|
83
|
+
"checksums",
|
|
84
|
+
"ctime",
|
|
85
|
+
"description",
|
|
86
|
+
"id",
|
|
87
|
+
"immutable_id",
|
|
88
|
+
"last_modified",
|
|
89
|
+
"media_type",
|
|
90
|
+
"modification_timestamp",
|
|
91
|
+
"mtime",
|
|
92
|
+
"name",
|
|
93
|
+
"size",
|
|
94
|
+
"type",
|
|
95
|
+
"url",
|
|
96
|
+
"url_stable_until",
|
|
97
|
+
"version",
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
_CALCULATIONS_INTRODUCED_IN = {name: "1.0" for name in ("id", "immutable_id", "last_modified", "type")}
|
|
101
|
+
|
|
39
102
|
register_optimade_entry_binding(
|
|
40
103
|
name="core-reference",
|
|
41
104
|
definition_id="https://schemas.optimade.org/defs/v1.2/entrytypes/optimade/references",
|
|
42
105
|
backend="httk.core.optimade.entries:OptimadeReference",
|
|
43
106
|
view="httk.core.optimade.entries:ReferenceView",
|
|
44
107
|
query_fields=None,
|
|
108
|
+
standard_property_versions=_REFERENCES_INTRODUCED_IN,
|
|
45
109
|
)
|
|
46
110
|
register_optimade_entry_binding(
|
|
47
111
|
name="core-file",
|
|
@@ -49,6 +113,7 @@ register_optimade_entry_binding(
|
|
|
49
113
|
backend="httk.core.optimade.entries:OptimadeFile",
|
|
50
114
|
view="httk.core.optimade.entries:FileView",
|
|
51
115
|
query_fields=None,
|
|
116
|
+
standard_property_versions=_FILES_INTRODUCED_IN,
|
|
52
117
|
)
|
|
53
118
|
register_optimade_entry_binding(
|
|
54
119
|
name="core-calculation",
|
|
@@ -56,6 +121,7 @@ register_optimade_entry_binding(
|
|
|
56
121
|
backend="httk.core.optimade.entries:OptimadeCalculation",
|
|
57
122
|
view="httk.core.optimade.entries:CalculationView",
|
|
58
123
|
query_fields=None,
|
|
124
|
+
standard_property_versions=_CALCULATIONS_INTRODUCED_IN,
|
|
59
125
|
)
|
|
60
126
|
register_entry_record(
|
|
61
127
|
name="core-file",
|
|
@@ -93,6 +93,7 @@ src/httk/core/docs/assets/selector.js
|
|
|
93
93
|
src/httk/core/optimade/__init__.py
|
|
94
94
|
src/httk/core/optimade/entries.py
|
|
95
95
|
src/httk/core/optimade/resources.py
|
|
96
|
+
src/httk/core/optimade/standard_names.py
|
|
96
97
|
src/httk/core/optimade/filter/README.md
|
|
97
98
|
src/httk/core/optimade/filter/__init__.py
|
|
98
99
|
src/httk/core/optimade/filter/_miniparser.py
|
|
@@ -214,6 +215,8 @@ tests/test_numeric.py
|
|
|
214
215
|
tests/test_operator_identity.py
|
|
215
216
|
tests/test_optimade_filter.py
|
|
216
217
|
tests/test_optimade_resources.py
|
|
218
|
+
tests/test_optimade_standard_names.py
|
|
219
|
+
tests/test_optimade_timestamps.py
|
|
217
220
|
tests/test_plugins_cli.py
|
|
218
221
|
tests/test_plugins_install.py
|
|
219
222
|
tests/test_plugins_manifest.py
|
|
@@ -231,6 +234,8 @@ tests/test_provenance.py
|
|
|
231
234
|
tests/test_records.py
|
|
232
235
|
tests/test_registry_discovery.py
|
|
233
236
|
tests/test_registry_tool.py
|
|
237
|
+
tests/test_release_checker.py
|
|
238
|
+
tests/test_release_prepare.py
|
|
234
239
|
tests/test_report.py
|
|
235
240
|
tests/test_saving.py
|
|
236
241
|
tests/test_schema_check.py
|