httk-core 2.1.0__tar.gz → 2.1.2__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.2}/PKG-INFO +1 -1
- {httk_core-2.1.0 → httk_core-2.1.2}/pyproject.toml +1 -1
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/__init__.py +5 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/data_records.py +24 -2
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/network_policy.py +1 -1
- httk_core-2.1.2/src/httk/core/entry_records.py +238 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/exactmath.py +198 -44
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/__init__.py +14 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/entries.py +99 -4
- httk_core-2.1.2/src/httk/core/optimade/standard_names.py +260 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/entries.py +27 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/fracvector.py +6 -1
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/surdvector.py +38 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/entries/core/__init__.py +66 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/PKG-INFO +1 -1
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/SOURCES.txt +7 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_data_records.py +23 -2
- httk_core-2.1.2/tests/test_entry_records.py +212 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_exactmath.py +5 -5
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_exactmath_api.py +55 -4
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_fracvector.py +11 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_network_policy.py +2 -2
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_optimade_resources.py +13 -6
- httk_core-2.1.2/tests/test_optimade_standard_names.py +362 -0
- httk_core-2.1.2/tests/test_optimade_timestamps.py +158 -0
- httk_core-2.1.2/tests/test_release_checker.py +176 -0
- httk_core-2.1.2/tests/test_release_prepare.py +191 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_surdvector.py +11 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/LICENSE +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/setup.cfg +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_discover.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_json.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_plugins.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_registry_tool.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/_sentinel.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/atomic_write.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/building.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/citations.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/converting.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/crypto.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/dataset_loader.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datasets.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_bytes.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_bytes_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_common.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_file.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_file_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_filename.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_filename_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_request.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_request_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_url_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/bytestream_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/compression.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/datastream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_common.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_file.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_file_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_filename.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_filename_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_request.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_request_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_string.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_string_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_url_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/datastream/textstream_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/digests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/__main__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/assets/selector.css +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/assets/selector.js +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/config.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/ecosystem.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/gitsite.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/inventories.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/lockfile.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/redirect.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/release.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/semver.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/sitetree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/docs/sphinx_ext.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/entry_ids.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/entry_provider.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/entry_types.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/fetching.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/files.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/identity_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/loading.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/memguard.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/filter/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/filter/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/filter/_miniparser.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/filter/optimade_filter_grammar.ebnf +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/filter/parser.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/optimade/resources.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/plugins/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/plugins/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/plugins/install.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/plugins/installed.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/plugins/manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/precision.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/anchor.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/export.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/sealing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/project/templates.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/property_definitions.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/provenance.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/py.typed +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/_base.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/io.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/register/schemas.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/report.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/saving.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/schema_check.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/services.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/storage/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/storage/identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/storage/markers.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/storage/rational_text.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/storage/stored_properties.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/system.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/userdirs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/validation/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/validation/iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/_nested.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/_squarefree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/leaf_codecs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/mutablefracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/numeric.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/scalar_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_api.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_frac_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_like.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_native_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_native_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_numpy_backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_numpy_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_surd_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vector_view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/vectors/vectormath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/backend.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/coercion.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/unviewing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/unwrapping.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/core/views/view.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/cli/core/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/LICENSE +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/LICENSE.httk +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/README.md +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/__init__.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/calculations.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/files.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/records.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/references.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk/registry/schemas/core/runs.json +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/dependency_links.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/entry_points.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/requires.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/src/httk_core.egg-info/top_level.txt +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_building.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_building_engine.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_citations.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_coercion.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_compression.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_converting.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_crypto.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_dataset_loader.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_datasets.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_datastream_url.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_digests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_config.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_ecosystem.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_gitsite.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_integration.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_inventories_release.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_lockfile.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_manifests_redirect.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_repair.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_semver.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_sitetree.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_docs_sphinx_ext.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_entry_ids.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_entry_provider.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_entry_types.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_examples.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_fetching.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_files.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_identity_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_leaf_codecs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_load_many.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_loading.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_memguard.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_mutablefracvector.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_numeric.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_operator_identity.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_optimade_filter.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_plugins_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_plugins_install.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_plugins_manifest.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_precision.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_cli_seal.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_export.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_init_template.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_manifests.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_members.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_sealing.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_project_templates.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_property_definitions.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_provenance.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_records.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_registry_discovery.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_registry_tool.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_report.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_saving.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_schema_check.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_services.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_storage_markers.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_stored_properties.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_system_cli.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_textstream.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_unview.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_userdirs.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_validation_iris.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_vector_family.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_vector_hash_equality.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_vectormath.py +0 -0
- {httk_core-2.1.0 → httk_core-2.1.2}/tests/test_view_laziness.py +0 -0
|
@@ -60,6 +60,7 @@ from .entry_ids import (
|
|
|
60
60
|
parse_immutable_id,
|
|
61
61
|
)
|
|
62
62
|
from .entry_provider import EntryProvider, RelatedEntry
|
|
63
|
+
from .entry_records import DataEntryRecord, EntryRecord, Property, entry_record
|
|
63
64
|
from .entry_types import Calculation, File, Reference
|
|
64
65
|
from .fetching import fetch
|
|
65
66
|
from .files import FileEntry, FileRecord
|
|
@@ -144,6 +145,7 @@ __all__ = [
|
|
|
144
145
|
"CLIContext",
|
|
145
146
|
"Calculation",
|
|
146
147
|
"CompressionCodec",
|
|
148
|
+
"DataEntryRecord",
|
|
147
149
|
"DataRecord",
|
|
148
150
|
"DataRecordEntry",
|
|
149
151
|
"Dataset",
|
|
@@ -155,6 +157,7 @@ __all__ = [
|
|
|
155
157
|
"DatastreamLike",
|
|
156
158
|
"DatastreamURL",
|
|
157
159
|
"EntryProvider",
|
|
160
|
+
"EntryRecord",
|
|
158
161
|
"EntryTypeDefinition",
|
|
159
162
|
"File",
|
|
160
163
|
"FileEntry",
|
|
@@ -167,6 +170,7 @@ __all__ = [
|
|
|
167
170
|
"MutableFracVector",
|
|
168
171
|
"NumericVector",
|
|
169
172
|
"ProductLink",
|
|
173
|
+
"Property",
|
|
170
174
|
"PropertyDefinition",
|
|
171
175
|
"Reference",
|
|
172
176
|
"Related",
|
|
@@ -199,6 +203,7 @@ __all__ = [
|
|
|
199
203
|
"content_id",
|
|
200
204
|
"credits",
|
|
201
205
|
"decimal_precision",
|
|
206
|
+
"entry_record",
|
|
202
207
|
"fetch",
|
|
203
208
|
"format_alternative_id",
|
|
204
209
|
"format_entry_id",
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
import datetime
|
|
4
4
|
import json
|
|
5
5
|
import math
|
|
6
|
-
from collections.abc import Mapping
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
7
7
|
from dataclasses import dataclass, field, fields
|
|
8
8
|
from typing import Annotated, Any, ClassVar, Self
|
|
9
9
|
|
|
10
|
-
from .
|
|
10
|
+
from .provenance import RunEdge, _edges
|
|
11
|
+
from .storage import IdentitySkip, Indexed, StorageInfo, StrongLink, Unique, stored_property
|
|
11
12
|
|
|
12
13
|
RECORDS_DEFINITION_ID = "https://schemas.httk.org/defs/v0.1/entrytypes/records"
|
|
13
14
|
_CANONICAL_JSON_ERROR = "value_json must be canonical JSON — use DataRecord.from_value."
|
|
@@ -60,9 +61,19 @@ class DataRecord:
|
|
|
60
61
|
The human-readable and immutable identifiers and timestamp metadata are
|
|
61
62
|
excluded from content identity.
|
|
62
63
|
|
|
64
|
+
``product_of`` names the entries this value describes, as
|
|
65
|
+
:class:`~httk.core.storage.StrongLink` edges with the same
|
|
66
|
+
:class:`~httk.core.provenance.RunEdge` scheme a :class:`~httk.core.Run` uses:
|
|
67
|
+
string ``(label, entry_type, entry_id)`` triples, part of the record's content
|
|
68
|
+
and therefore pinned to the exact entry revision. It is served forward as the
|
|
69
|
+
``product_of`` relationship and in reverse as ``has_product`` on the target,
|
|
70
|
+
and is searchable through ``record.links.product_of`` and
|
|
71
|
+
``target.links.has_product``.
|
|
72
|
+
|
|
63
73
|
:param definition_id: The property definition IRI for the value.
|
|
64
74
|
:param name: The property name.
|
|
65
75
|
:param value_json: The canonical JSON representation of the value.
|
|
76
|
+
:param product_of: The entries this value is a product of, as labeled edges.
|
|
66
77
|
:param id: The human-readable entry id shared by all revisions; minted by the store when None.
|
|
67
78
|
:param immutable_id: The per-revision immutable id; minted by the store when None.
|
|
68
79
|
:param last_modified: The optional timezone-aware metadata timestamp.
|
|
@@ -77,6 +88,7 @@ class DataRecord:
|
|
|
77
88
|
definition_id: str
|
|
78
89
|
name: str
|
|
79
90
|
value_json: str
|
|
91
|
+
product_of: Annotated[tuple[RunEdge, ...], StrongLink("product_of", reverse="has_product", role="subject")] = ()
|
|
80
92
|
id: Annotated[str | None, IdentitySkip(), Indexed()] = field(default=None, compare=False)
|
|
81
93
|
immutable_id: Annotated[str | None, IdentitySkip(), Unique()] = field(default=None, compare=False)
|
|
82
94
|
last_modified: Annotated[datetime.datetime | None, IdentitySkip()] = field(default=None, compare=False)
|
|
@@ -115,6 +127,13 @@ class DataRecord:
|
|
|
115
127
|
raise ValueError(_CANONICAL_JSON_ERROR) from exc
|
|
116
128
|
if canonical != self.value_json:
|
|
117
129
|
raise ValueError(_CANONICAL_JSON_ERROR)
|
|
130
|
+
edges = _edges(self.product_of)
|
|
131
|
+
labels: set[str] = set()
|
|
132
|
+
for edge in edges:
|
|
133
|
+
if edge.label in labels:
|
|
134
|
+
raise ValueError(f"Duplicate label {edge.label!r} on DataRecord product_of.")
|
|
135
|
+
labels.add(edge.label)
|
|
136
|
+
object.__setattr__(self, "product_of", edges)
|
|
118
137
|
_validate_timestamp(self.last_modified, "last_modified")
|
|
119
138
|
|
|
120
139
|
@classmethod
|
|
@@ -124,6 +143,7 @@ class DataRecord:
|
|
|
124
143
|
name: str,
|
|
125
144
|
value: Any,
|
|
126
145
|
*,
|
|
146
|
+
product_of: Iterable[RunEdge | Mapping[str, Any]] = (),
|
|
127
147
|
id: str | None = None,
|
|
128
148
|
immutable_id: str | None = None,
|
|
129
149
|
last_modified: datetime.datetime | None = None,
|
|
@@ -133,6 +153,7 @@ class DataRecord:
|
|
|
133
153
|
:param definition_id: The property definition IRI for the value.
|
|
134
154
|
:param name: The property name.
|
|
135
155
|
:param value: The JSON value to encode.
|
|
156
|
+
:param product_of: The entries this value is a product of, as labeled edges.
|
|
136
157
|
:param id: The human-readable entry id shared by all revisions; minted by the store when None.
|
|
137
158
|
:param immutable_id: The per-revision immutable id; minted by the store when None.
|
|
138
159
|
:param last_modified: The optional timezone-aware metadata timestamp.
|
|
@@ -144,6 +165,7 @@ class DataRecord:
|
|
|
144
165
|
definition_id,
|
|
145
166
|
name,
|
|
146
167
|
json.dumps(value, sort_keys=True, separators=(",", ":"), allow_nan=False),
|
|
168
|
+
product_of=_edges(product_of),
|
|
147
169
|
id=id,
|
|
148
170
|
immutable_id=immutable_id,
|
|
149
171
|
last_modified=last_modified,
|
|
@@ -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"})
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""Declarative frozen records with explicit OPTIMADE property metadata."""
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
import hashlib
|
|
5
|
+
import types
|
|
6
|
+
from collections.abc import Callable, Mapping
|
|
7
|
+
from dataclasses import dataclass, field, fields, replace
|
|
8
|
+
from inspect import get_annotations
|
|
9
|
+
from typing import Annotated, Any, ClassVar, Union, dataclass_transform, get_args, get_origin, get_type_hints
|
|
10
|
+
|
|
11
|
+
from .data_records import RECORDS_DEFINITION_ID
|
|
12
|
+
from .property_definitions import EntryTypeDefinition, PropertyDefinition, known_definition_prefixes
|
|
13
|
+
from .register.schemas import load_entry_type_definition
|
|
14
|
+
from .storage import IdentitySkip, Indexed, Skip, StorageInfo, StoredPropertyProjection, Unique
|
|
15
|
+
from .storage.stored_properties import QueryContext, QueryExpression
|
|
16
|
+
|
|
17
|
+
__all__ = ["DataEntryRecord", "EntryRecord", "Property", "entry_record"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True, kw_only=True)
|
|
21
|
+
class EntryRecord:
|
|
22
|
+
"""Common metadata for application-defined served records.
|
|
23
|
+
|
|
24
|
+
Subclasses declare their entry family's ``type`` and ``definition_id`` and
|
|
25
|
+
use :func:`entry_record` to become concrete frozen records. Metadata fields
|
|
26
|
+
are keyword-only and excluded from equality and content identity.
|
|
27
|
+
|
|
28
|
+
:param id: Public lineage identifier, assigned by the store when omitted.
|
|
29
|
+
:param immutable_id: Revision identifier, assigned by the store when omitted.
|
|
30
|
+
:param last_modified: Optional timezone-aware source modification time.
|
|
31
|
+
:raises ValueError: If an identifier is empty or a timestamp lacks a timezone.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
id: Annotated[str | None, IdentitySkip(), Indexed()] = field(default=None, compare=False)
|
|
35
|
+
immutable_id: Annotated[str | None, IdentitySkip(), Unique()] = field(default=None, compare=False)
|
|
36
|
+
last_modified: Annotated[datetime.datetime | None, IdentitySkip()] = field(default=None, compare=False)
|
|
37
|
+
|
|
38
|
+
type: ClassVar[str]
|
|
39
|
+
definition_id: ClassVar[str]
|
|
40
|
+
__httk_entry_name__: ClassVar[str]
|
|
41
|
+
__httk_property_definitions__: ClassVar[Mapping[str, PropertyDefinition]]
|
|
42
|
+
__httk_stored_properties__: ClassVar[Mapping[str, StoredPropertyProjection]]
|
|
43
|
+
__httk_storage__: ClassVar[StorageInfo]
|
|
44
|
+
|
|
45
|
+
def __post_init__(self) -> None:
|
|
46
|
+
"""Validate explicitly supplied entry metadata."""
|
|
47
|
+
for name in ("id", "immutable_id"):
|
|
48
|
+
value = getattr(self, name)
|
|
49
|
+
if value is not None and (not isinstance(value, str) or not value or value != value.strip()):
|
|
50
|
+
raise ValueError(f"{name} must be a nonempty string without surrounding whitespace or None")
|
|
51
|
+
if self.last_modified is not None and (
|
|
52
|
+
not isinstance(self.last_modified, datetime.datetime) or self.last_modified.utcoffset() is None
|
|
53
|
+
):
|
|
54
|
+
raise ValueError("last_modified must be a timezone-aware datetime or None")
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def entry_type_definition(cls) -> EntryTypeDefinition:
|
|
58
|
+
"""Return this concrete record's extended entry-family definition.
|
|
59
|
+
|
|
60
|
+
:return: The base family definition extended with declared properties.
|
|
61
|
+
:raises TypeError: If the class was not decorated with :func:`entry_record`.
|
|
62
|
+
:raises ValueError: If a standard property definition is replaced.
|
|
63
|
+
"""
|
|
64
|
+
if "__httk_property_definitions__" not in vars(cls):
|
|
65
|
+
raise TypeError(f"{cls.__name__} must be decorated with entry_record")
|
|
66
|
+
base = load_entry_type_definition(cls.definition_id)
|
|
67
|
+
extra = {}
|
|
68
|
+
for name, definition in cls.__httk_property_definitions__.items():
|
|
69
|
+
if name in base.properties:
|
|
70
|
+
if definition.as_optimade() != base.properties[name].as_optimade():
|
|
71
|
+
raise ValueError(f"{name}: a standard property definition cannot be replaced")
|
|
72
|
+
else:
|
|
73
|
+
extra[name] = definition
|
|
74
|
+
return base.extended(extra)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class DataEntryRecord(EntryRecord):
|
|
78
|
+
"""Base for typed application records served at ``_httk_records``.
|
|
79
|
+
|
|
80
|
+
No scientific fields are prescribed. This does not replace the existing
|
|
81
|
+
:class:`~httk.core.data_records.DataRecord` single-property value model.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
type: ClassVar[str] = "records"
|
|
85
|
+
definition_id: ClassVar[str] = RECORDS_DEFINITION_ID
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass(frozen=True, kw_only=True)
|
|
89
|
+
class Property:
|
|
90
|
+
"""Declare a served scalar field through ``Annotated`` metadata.
|
|
91
|
+
|
|
92
|
+
The Python annotation supplies the type (``str``, ``int``, ``float``, or
|
|
93
|
+
``bool``, optionally nullable). Only marked fields become served attributes.
|
|
94
|
+
Use an existing :class:`~httk.core.property_definitions.PropertyDefinition`
|
|
95
|
+
as the annotation metadata instead when a curated definition exists.
|
|
96
|
+
|
|
97
|
+
:param description: Scientific meaning of the field, including normalization.
|
|
98
|
+
:param unit: Property unit; omitted means dimensionless.
|
|
99
|
+
:param name: Served name; omitted uses ``_httk_custom_<field>``.
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
description: str
|
|
103
|
+
unit: str | None = None
|
|
104
|
+
name: str | None = None
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _scalar_type(annotation: Any) -> tuple[str, bool]:
|
|
108
|
+
"""Resolve the exact scalar types supported by direct field mappings."""
|
|
109
|
+
nullable = False
|
|
110
|
+
if get_origin(annotation) in (types.UnionType, Union):
|
|
111
|
+
args = get_args(annotation)
|
|
112
|
+
if len(args) == 2 and type(None) in args:
|
|
113
|
+
annotation = next(arg for arg in args if arg is not type(None))
|
|
114
|
+
nullable = True
|
|
115
|
+
names = {str: "string", int: "integer", float: "float", bool: "boolean"}
|
|
116
|
+
if annotation not in names:
|
|
117
|
+
raise TypeError(
|
|
118
|
+
"Property fields require str, int, float, or bool (optionally | None); use explicit projections otherwise"
|
|
119
|
+
)
|
|
120
|
+
return names[annotation], nullable
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _projection(field_name: str) -> StoredPropertyProjection:
|
|
124
|
+
"""Build independent direct-field callbacks for one scalar property."""
|
|
125
|
+
|
|
126
|
+
def query(context: QueryContext, operator: str, value: object) -> QueryExpression:
|
|
127
|
+
return context.compare(context.field(field_name), operator, context.constant(value))
|
|
128
|
+
|
|
129
|
+
return StoredPropertyProjection(
|
|
130
|
+
response=lambda record: getattr(record, field_name),
|
|
131
|
+
query=query,
|
|
132
|
+
sort=lambda context: context.field(field_name),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
@dataclass_transform(frozen_default=True)
|
|
137
|
+
def entry_record[T: EntryRecord](name: str) -> Callable[[type[T]], type[T]]:
|
|
138
|
+
"""Declare a frozen served record with a stable, explicit identity.
|
|
139
|
+
|
|
140
|
+
This decorator does not register global plugins. Pass the resulting class
|
|
141
|
+
explicitly to the store on creation and reopening. Referenced records remain
|
|
142
|
+
ordinary dataclass fields. Property mappings are compiled for each concrete
|
|
143
|
+
class; subclasses must opt in with their own decoration and identity.
|
|
144
|
+
|
|
145
|
+
:param name: Stable logical identity, independent of the Python module/name.
|
|
146
|
+
:return: A decorator that creates a frozen dataclass and its served mappings.
|
|
147
|
+
:raises TypeError: If the class, field type, or declaration is unsupported.
|
|
148
|
+
:raises ValueError: If identities, property names, or definitions conflict.
|
|
149
|
+
"""
|
|
150
|
+
if not isinstance(name, str) or not name or name != name.strip():
|
|
151
|
+
raise ValueError("entry_record requires a nonempty stable name without surrounding whitespace")
|
|
152
|
+
|
|
153
|
+
def decorate(cls: type[T]) -> type[T]:
|
|
154
|
+
if not issubclass(cls, EntryRecord):
|
|
155
|
+
raise TypeError("entry_record requires an EntryRecord subclass")
|
|
156
|
+
if "__dataclass_fields__" in vars(cls):
|
|
157
|
+
raise TypeError("entry_record replaces @dataclass; do not apply both decorators")
|
|
158
|
+
# Resolve this class's own annotations, including deferred ones on Python 3.14.
|
|
159
|
+
if any(key in get_annotations(cls) for key in ("id", "immutable_id", "last_modified")):
|
|
160
|
+
raise TypeError("entry_record metadata fields must retain the EntryRecord declarations")
|
|
161
|
+
if "__httk_stored_properties__" in vars(cls) or "__httk_property_definitions__" in vars(cls):
|
|
162
|
+
raise TypeError(
|
|
163
|
+
"entry_record generates property declarations; use an explicit record for custom projections"
|
|
164
|
+
)
|
|
165
|
+
if not isinstance(getattr(cls, "definition_id", None), str) or not isinstance(getattr(cls, "type", None), str):
|
|
166
|
+
raise TypeError("EntryRecord subclasses must declare type and definition_id")
|
|
167
|
+
base = load_entry_type_definition(cls.definition_id)
|
|
168
|
+
if cls.type != base.name:
|
|
169
|
+
raise ValueError("record type must match its entry-family definition")
|
|
170
|
+
storage = vars(cls).get("__httk_storage__", StorageInfo())
|
|
171
|
+
if not isinstance(storage, StorageInfo):
|
|
172
|
+
raise TypeError("__httk_storage__ must be StorageInfo")
|
|
173
|
+
if storage.identity_name not in (None, name):
|
|
174
|
+
raise ValueError("StorageInfo.identity_name conflicts with the entry_record name")
|
|
175
|
+
cls = dataclass(frozen=True)(cls)
|
|
176
|
+
hints = get_type_hints(cls, localns={cls.__name__: cls}, include_extras=True)
|
|
177
|
+
for field_name, hint in hints.items():
|
|
178
|
+
if get_origin(hint) is ClassVar:
|
|
179
|
+
inner = get_args(hint)[0]
|
|
180
|
+
if get_origin(inner) is Annotated and any(
|
|
181
|
+
isinstance(marker, (Property, PropertyDefinition)) for marker in get_args(inner)[1:]
|
|
182
|
+
):
|
|
183
|
+
raise TypeError(f"{field_name}: a served property must be an instance field, not ClassVar")
|
|
184
|
+
definitions: dict[str, PropertyDefinition] = {}
|
|
185
|
+
projections: dict[str, StoredPropertyProjection] = {}
|
|
186
|
+
for item in fields(cls):
|
|
187
|
+
hint = hints[item.name]
|
|
188
|
+
if get_origin(hint) is not Annotated:
|
|
189
|
+
continue
|
|
190
|
+
annotation, *metadata = get_args(hint)
|
|
191
|
+
markers = [marker for marker in metadata if isinstance(marker, (Property, PropertyDefinition))]
|
|
192
|
+
if not markers:
|
|
193
|
+
continue
|
|
194
|
+
if len(markers) != 1:
|
|
195
|
+
raise ValueError(f"{item.name} must have exactly one property declaration")
|
|
196
|
+
if any(isinstance(marker, IdentitySkip) for marker in metadata):
|
|
197
|
+
raise ValueError(f"{item.name}: identity metadata cannot declare a scientific property")
|
|
198
|
+
if any(isinstance(marker, Skip) for marker in metadata):
|
|
199
|
+
raise ValueError(f"{item.name}: a skipped field cannot declare a served property")
|
|
200
|
+
fulltype, nullable = _scalar_type(annotation)
|
|
201
|
+
marker = markers[0]
|
|
202
|
+
if isinstance(marker, Property):
|
|
203
|
+
if not isinstance(marker.description, str) or not marker.description.strip():
|
|
204
|
+
raise ValueError(f"{item.name}: Property requires a nonempty description")
|
|
205
|
+
if marker.name is not None and (not isinstance(marker.name, str) or not marker.name.isidentifier()):
|
|
206
|
+
raise ValueError(f"{item.name}: Property.name must be a nonempty identifier")
|
|
207
|
+
definition = PropertyDefinition.from_simple(
|
|
208
|
+
marker.name or f"_httk_custom_{item.name}",
|
|
209
|
+
description=marker.description,
|
|
210
|
+
fulltype=fulltype,
|
|
211
|
+
unit=marker.unit,
|
|
212
|
+
)
|
|
213
|
+
else:
|
|
214
|
+
definition = marker
|
|
215
|
+
if definition.optimade_type != fulltype or (nullable and not definition.nullable):
|
|
216
|
+
raise ValueError(f"{item.name}: property definition conflicts with the Python field type")
|
|
217
|
+
served_name = definition.name
|
|
218
|
+
if served_name not in base.properties and not any(
|
|
219
|
+
served_name.startswith(prefix) for prefix in known_definition_prefixes()
|
|
220
|
+
):
|
|
221
|
+
raise ValueError(f"{served_name}: custom property names require a registered provider prefix")
|
|
222
|
+
if served_name in definitions or served_name in ("id", "type", "immutable_id", "last_modified"):
|
|
223
|
+
raise ValueError(f"duplicate or reserved served property name {served_name!r}")
|
|
224
|
+
definitions[served_name] = definition
|
|
225
|
+
projections[served_name] = _projection(item.name)
|
|
226
|
+
cls.__httk_entry_name__ = name
|
|
227
|
+
cls.__httk_storage__ = replace(
|
|
228
|
+
storage,
|
|
229
|
+
identity_name=name,
|
|
230
|
+
storage_name=storage.storage_name or "entry_" + hashlib.sha256(name.encode()).hexdigest()[:24],
|
|
231
|
+
)
|
|
232
|
+
cls.__httk_property_definitions__ = definitions
|
|
233
|
+
cls.__httk_stored_properties__ = projections
|
|
234
|
+
# Validate extension compatibility at definition time, not the first request.
|
|
235
|
+
cls.entry_type_definition()
|
|
236
|
+
return cls
|
|
237
|
+
|
|
238
|
+
return decorate
|