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.
Files changed (256) hide show
  1. {httk_core-2.1.0 → httk_core-2.1.1}/PKG-INFO +1 -1
  2. {httk_core-2.1.0 → httk_core-2.1.1}/pyproject.toml +1 -1
  3. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/network_policy.py +1 -1
  4. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/__init__.py +14 -0
  5. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/entries.py +99 -4
  6. httk_core-2.1.1/src/httk/core/optimade/standard_names.py +260 -0
  7. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/entries.py +27 -0
  8. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/entries/core/__init__.py +66 -0
  9. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/PKG-INFO +1 -1
  10. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/SOURCES.txt +5 -0
  11. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_network_policy.py +2 -2
  12. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_optimade_resources.py +13 -6
  13. httk_core-2.1.1/tests/test_optimade_standard_names.py +362 -0
  14. httk_core-2.1.1/tests/test_optimade_timestamps.py +158 -0
  15. httk_core-2.1.1/tests/test_release_checker.py +176 -0
  16. httk_core-2.1.1/tests/test_release_prepare.py +191 -0
  17. {httk_core-2.1.0 → httk_core-2.1.1}/LICENSE +0 -0
  18. {httk_core-2.1.0 → httk_core-2.1.1}/README.md +0 -0
  19. {httk_core-2.1.0 → httk_core-2.1.1}/setup.cfg +0 -0
  20. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/__init__.py +0 -0
  21. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_discover.py +0 -0
  22. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_iris.py +0 -0
  23. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_json.py +0 -0
  24. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_manifest.py +0 -0
  25. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_plugins.py +0 -0
  26. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_registry_tool.py +0 -0
  27. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/_sentinel.py +0 -0
  28. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/atomic_write.py +0 -0
  29. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/building.py +0 -0
  30. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/citations.py +0 -0
  31. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/cli.py +0 -0
  32. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/converting.py +0 -0
  33. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/crypto.py +0 -0
  34. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/data_records.py +0 -0
  35. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/dataset_loader.py +0 -0
  36. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datasets.py +0 -0
  37. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/__init__.py +0 -0
  38. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_api.py +0 -0
  39. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_backend.py +0 -0
  40. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_bytes.py +0 -0
  41. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_bytes_view.py +0 -0
  42. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_common.py +0 -0
  43. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_file.py +0 -0
  44. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_file_view.py +0 -0
  45. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_filename.py +0 -0
  46. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_filename_view.py +0 -0
  47. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_like.py +0 -0
  48. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_request.py +0 -0
  49. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_request_view.py +0 -0
  50. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_url.py +0 -0
  51. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_url_view.py +0 -0
  52. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/bytestream_view.py +0 -0
  53. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/compression.py +0 -0
  54. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/datastream_url.py +0 -0
  55. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_api.py +0 -0
  56. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_backend.py +0 -0
  57. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_common.py +0 -0
  58. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_file.py +0 -0
  59. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_file_view.py +0 -0
  60. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_filename.py +0 -0
  61. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_filename_view.py +0 -0
  62. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_like.py +0 -0
  63. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_request.py +0 -0
  64. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_request_view.py +0 -0
  65. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_string.py +0 -0
  66. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_string_view.py +0 -0
  67. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_url.py +0 -0
  68. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_url_view.py +0 -0
  69. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/datastream/textstream_view.py +0 -0
  70. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/digests.py +0 -0
  71. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/__init__.py +0 -0
  72. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/__main__.py +0 -0
  73. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/assets/selector.css +0 -0
  74. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/assets/selector.js +0 -0
  75. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/cli.py +0 -0
  76. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/config.py +0 -0
  77. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/ecosystem.py +0 -0
  78. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/gitsite.py +0 -0
  79. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/inventories.py +0 -0
  80. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/lockfile.py +0 -0
  81. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/manifests.py +0 -0
  82. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/redirect.py +0 -0
  83. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/release.py +0 -0
  84. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/semver.py +0 -0
  85. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/sitetree.py +0 -0
  86. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/docs/sphinx_ext.py +0 -0
  87. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_ids.py +0 -0
  88. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_provider.py +0 -0
  89. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/entry_types.py +0 -0
  90. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/exactmath.py +0 -0
  91. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/fetching.py +0 -0
  92. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/files.py +0 -0
  93. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/identity.py +0 -0
  94. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/identity_cli.py +0 -0
  95. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/loading.py +0 -0
  96. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/memguard.py +0 -0
  97. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/README.md +0 -0
  98. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/__init__.py +0 -0
  99. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/_miniparser.py +0 -0
  100. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/optimade_filter_grammar.ebnf +0 -0
  101. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/filter/parser.py +0 -0
  102. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/optimade/resources.py +0 -0
  103. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/__init__.py +0 -0
  104. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/cli.py +0 -0
  105. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/install.py +0 -0
  106. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/installed.py +0 -0
  107. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/plugins/manifest.py +0 -0
  108. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/precision.py +0 -0
  109. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/__init__.py +0 -0
  110. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/anchor.py +0 -0
  111. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/cli.py +0 -0
  112. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/export.py +0 -0
  113. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/manifests.py +0 -0
  114. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/members.py +0 -0
  115. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/sealing.py +0 -0
  116. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/project/templates.py +0 -0
  117. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/property_definitions.py +0 -0
  118. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/provenance.py +0 -0
  119. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/py.typed +0 -0
  120. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/records.py +0 -0
  121. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/__init__.py +0 -0
  122. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/_base.py +0 -0
  123. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/cli.py +0 -0
  124. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/io.py +0 -0
  125. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/members.py +0 -0
  126. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/register/schemas.py +0 -0
  127. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/report.py +0 -0
  128. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/saving.py +0 -0
  129. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/schema_check.py +0 -0
  130. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/services.py +0 -0
  131. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/__init__.py +0 -0
  132. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/identity.py +0 -0
  133. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/markers.py +0 -0
  134. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/rational_text.py +0 -0
  135. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/storage/stored_properties.py +0 -0
  136. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/system.py +0 -0
  137. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/userdirs.py +0 -0
  138. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/validation/__init__.py +0 -0
  139. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/validation/iris.py +0 -0
  140. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/__init__.py +0 -0
  141. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/_nested.py +0 -0
  142. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/_squarefree.py +0 -0
  143. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/fracvector.py +0 -0
  144. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/leaf_codecs.py +0 -0
  145. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/mutablefracvector.py +0 -0
  146. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/numeric.py +0 -0
  147. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/scalar_like.py +0 -0
  148. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/surdvector.py +0 -0
  149. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_api.py +0 -0
  150. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_backend.py +0 -0
  151. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_frac_view.py +0 -0
  152. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_like.py +0 -0
  153. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_native_backend.py +0 -0
  154. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_native_view.py +0 -0
  155. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_numpy_backend.py +0 -0
  156. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_numpy_view.py +0 -0
  157. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_surd_view.py +0 -0
  158. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vector_view.py +0 -0
  159. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/vectors/vectormath.py +0 -0
  160. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/__init__.py +0 -0
  161. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/backend.py +0 -0
  162. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/coercion.py +0 -0
  163. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/unviewing.py +0 -0
  164. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/unwrapping.py +0 -0
  165. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/core/views/view.py +0 -0
  166. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/cli/core/__init__.py +0 -0
  167. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/LICENSE +0 -0
  168. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/LICENSE.httk +0 -0
  169. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/README.md +0 -0
  170. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/__init__.py +0 -0
  171. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/calculations.json +0 -0
  172. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/files.json +0 -0
  173. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/records.json +0 -0
  174. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/references.json +0 -0
  175. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk/registry/schemas/core/runs.json +0 -0
  176. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/dependency_links.txt +0 -0
  177. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/entry_points.txt +0 -0
  178. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/requires.txt +0 -0
  179. {httk_core-2.1.0 → httk_core-2.1.1}/src/httk_core.egg-info/top_level.txt +0 -0
  180. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_building.py +0 -0
  181. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_building_engine.py +0 -0
  182. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_citations.py +0 -0
  183. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_cli.py +0 -0
  184. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_coercion.py +0 -0
  185. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_compression.py +0 -0
  186. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_converting.py +0 -0
  187. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_crypto.py +0 -0
  188. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_data_records.py +0 -0
  189. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_dataset_loader.py +0 -0
  190. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_datasets.py +0 -0
  191. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_datastream_url.py +0 -0
  192. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_digests.py +0 -0
  193. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_cli.py +0 -0
  194. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_config.py +0 -0
  195. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_ecosystem.py +0 -0
  196. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_gitsite.py +0 -0
  197. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_integration.py +0 -0
  198. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_inventories_release.py +0 -0
  199. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_lockfile.py +0 -0
  200. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_manifests_redirect.py +0 -0
  201. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_repair.py +0 -0
  202. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_semver.py +0 -0
  203. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_sitetree.py +0 -0
  204. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_docs_sphinx_ext.py +0 -0
  205. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_ids.py +0 -0
  206. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_provider.py +0 -0
  207. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_entry_types.py +0 -0
  208. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_exactmath.py +0 -0
  209. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_exactmath_api.py +0 -0
  210. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_examples.py +0 -0
  211. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_fetching.py +0 -0
  212. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_files.py +0 -0
  213. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_fracvector.py +0 -0
  214. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_identity.py +0 -0
  215. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_identity_cli.py +0 -0
  216. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_leaf_codecs.py +0 -0
  217. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_load_many.py +0 -0
  218. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_loading.py +0 -0
  219. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_memguard.py +0 -0
  220. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_mutablefracvector.py +0 -0
  221. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_numeric.py +0 -0
  222. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_operator_identity.py +0 -0
  223. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_optimade_filter.py +0 -0
  224. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_cli.py +0 -0
  225. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_install.py +0 -0
  226. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_plugins_manifest.py +0 -0
  227. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_precision.py +0 -0
  228. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project.py +0 -0
  229. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_cli_seal.py +0 -0
  230. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_export.py +0 -0
  231. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_init_template.py +0 -0
  232. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_manifests.py +0 -0
  233. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_members.py +0 -0
  234. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_sealing.py +0 -0
  235. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_project_templates.py +0 -0
  236. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_property_definitions.py +0 -0
  237. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_provenance.py +0 -0
  238. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_records.py +0 -0
  239. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_registry_discovery.py +0 -0
  240. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_registry_tool.py +0 -0
  241. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_report.py +0 -0
  242. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_saving.py +0 -0
  243. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_schema_check.py +0 -0
  244. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_services.py +0 -0
  245. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_storage_markers.py +0 -0
  246. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_stored_properties.py +0 -0
  247. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_surdvector.py +0 -0
  248. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_system_cli.py +0 -0
  249. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_textstream.py +0 -0
  250. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_unview.py +0 -0
  251. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_userdirs.py +0 -0
  252. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_validation_iris.py +0 -0
  253. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vector_family.py +0 -0
  254. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vector_hash_equality.py +0 -0
  255. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_vectormath.py +0 -0
  256. {httk_core-2.1.0 → httk_core-2.1.1}/tests/test_view_laziness.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httk-core
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: httk₂ core functionality
5
5
  Author-email: Rickard Armiento <rickard-gpg@armiento.net>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "httk-core"
7
- version = "2.1.0"
7
+ version = "2.1.1"
8
8
  description = "httk₂ core functionality"
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
@@ -1,6 +1,6 @@
1
1
  """Policy and timeout helpers for network-backed datastream opens."""
2
2
 
3
- DEFAULT_NETWORK_TIMEOUT: float | None = 30.0
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
- return cast(datetime.datetime | None, self._portable_value("last_modified", datetime.datetime))
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
- values[record_field.name] = self._backend.decode_value(definition, raw)
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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: httk-core
3
- Version: 2.1.0
3
+ Version: 2.1.1
4
4
  Summary: httk₂ core functionality
5
5
  Author-email: Rickard Armiento <rickard-gpg@armiento.net>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -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