arango-query-core 0.2.5__tar.gz → 0.2.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/CHANGELOG.md +13 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/PKG-INFO +1 -1
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/__init__.py +2 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/mapping.py +30 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/pyproject.toml +1 -1
- arango_query_core-0.2.6/tests/test_mapping_property_encoding.py +74 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/.cursor/rules/fix-upstream-dont-fork.mdc +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/.github/workflows/ci.yml +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/.github/workflows/publish.yml +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/.gitignore +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/LICENSE +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/README.md +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/aql.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/errors.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/exec.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/extensions.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/__init__.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/engine.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/fewshot.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/grounding.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/pathindex.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/postconditions.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/providers.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/seams.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/nl/synthbank.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/owl_rdflib.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/owl_turtle.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/arango_query_core/py.typed +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/docs/PRD.md +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/fixtures/mappings/pg.owl.ttl +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_mapping_helpers.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_mapping_resolver_normalization.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_mapping_supertypes.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_mapping_vci_detection.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_engine.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_engine_extraction_and_resilience.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_fewshot.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_grounding.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_pathindex.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_postconditions.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_providers.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_nl_synthbank.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_owl_rdflib.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_owl_turtle_roundtrip.py +0 -0
- {arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_upstream_markers.py +0 -0
|
@@ -6,6 +6,19 @@ exact versions (contextual-data-fabric CC-9) and re-run their golden /
|
|
|
6
6
|
eval suites on every bump — pre-1.0, **minor** bumps may break the API
|
|
7
7
|
and this file must say so explicitly.
|
|
8
8
|
|
|
9
|
+
## [0.2.6] - 2026-10-29
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Temporal property encodings.** A property may declare how its temporal
|
|
14
|
+
values are stored, `"encoding": "epoch_ms"` (integer milliseconds since the
|
|
15
|
+
epoch) or `"iso8601"` (an ISO-8601 string), read into
|
|
16
|
+
`PropertyInfo.encoding` (empty when not declared) and carried by
|
|
17
|
+
`schema_summary()`. Any other value is refused with `INVALID_MAPPING`.
|
|
18
|
+
The vocabulary is `TEMPORAL_ENCODINGS`. It is a physical detail, like
|
|
19
|
+
`field`, so OWL/Turtle does not carry it. arango-cypher-py lowers temporal
|
|
20
|
+
accessors, functions and arithmetic from it (LDBC WP-41).
|
|
21
|
+
|
|
9
22
|
## [0.2.5] - 2026-10-26
|
|
10
23
|
|
|
11
24
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: arango-query-core
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.6
|
|
4
4
|
Summary: Shared conceptual-model and NL substrate for ArangoDB query transpilers (arango-cypher-py, arango-sparql-py)
|
|
5
5
|
Project-URL: Repository, https://github.com/ArthurKeen/arango-query-core
|
|
6
6
|
Project-URL: Changelog, https://github.com/ArthurKeen/arango-query-core/blob/main/CHANGELOG.md
|
|
@@ -5,6 +5,7 @@ from .extensions import ExtensionPolicy, ExtensionRegistry
|
|
|
5
5
|
from .mapping import (
|
|
6
6
|
COLLECTION_NAME_RE,
|
|
7
7
|
SUPERTYPE_STYLES,
|
|
8
|
+
TEMPORAL_ENCODINGS,
|
|
8
9
|
IndexInfo,
|
|
9
10
|
MappingBundle,
|
|
10
11
|
MappingResolver,
|
|
@@ -25,6 +26,7 @@ except ImportError:
|
|
|
25
26
|
__all__ = [
|
|
26
27
|
"COLLECTION_NAME_RE",
|
|
27
28
|
"SUPERTYPE_STYLES",
|
|
29
|
+
"TEMPORAL_ENCODINGS",
|
|
28
30
|
"AqlExecutor",
|
|
29
31
|
"AqlFragment",
|
|
30
32
|
"AqlQuery",
|
|
@@ -163,6 +163,14 @@ class IndexInfo:
|
|
|
163
163
|
traversal_safe: bool = True
|
|
164
164
|
|
|
165
165
|
|
|
166
|
+
#: Storage encodings of temporal property values.
|
|
167
|
+
#:
|
|
168
|
+
#: * ``epoch_ms`` — an integer, milliseconds since 1970-01-01T00:00:00Z.
|
|
169
|
+
#: * ``iso8601`` — an ISO-8601 string (``2010-01-03T15:10:31.499+00:00``,
|
|
170
|
+
#: or ``2010-01-03`` for a date).
|
|
171
|
+
TEMPORAL_ENCODINGS = frozenset({"epoch_ms", "iso8601"})
|
|
172
|
+
|
|
173
|
+
|
|
166
174
|
@dataclass(frozen=True)
|
|
167
175
|
class PropertyInfo:
|
|
168
176
|
"""Metadata for a single property/attribute on an entity or relationship.
|
|
@@ -187,6 +195,25 @@ class PropertyInfo:
|
|
|
187
195
|
# / numeric / free_text / boolean / other), inferred by the schema analyzer.
|
|
188
196
|
# Consumers choose a matching strategy from this rather than field names.
|
|
189
197
|
role: str = ""
|
|
198
|
+
# How a temporal value (``type`` date / datetime / …) is stored: one of
|
|
199
|
+
# ``TEMPORAL_ENCODINGS``, or empty when the mapping does not say.
|
|
200
|
+
# Consumers must not guess an encoding: comparisons, components
|
|
201
|
+
# (``.year``) and arithmetic differ between the two.
|
|
202
|
+
encoding: str = ""
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _property_encoding(owner: str, name: str, raw: Any) -> str:
|
|
206
|
+
"""A property's declared ``encoding``, checked against ``TEMPORAL_ENCODINGS``."""
|
|
207
|
+
if raw in (None, ""):
|
|
208
|
+
return ""
|
|
209
|
+
encoding = str(raw)
|
|
210
|
+
if encoding not in TEMPORAL_ENCODINGS:
|
|
211
|
+
raise CoreError(
|
|
212
|
+
f"Property {owner}.{name} declares encoding {encoding!r}; expected one of "
|
|
213
|
+
f"{', '.join(sorted(TEMPORAL_ENCODINGS))}",
|
|
214
|
+
code="INVALID_MAPPING",
|
|
215
|
+
)
|
|
216
|
+
return encoding
|
|
190
217
|
|
|
191
218
|
|
|
192
219
|
@dataclass(frozen=True)
|
|
@@ -438,6 +465,7 @@ class MappingResolver:
|
|
|
438
465
|
numeric_like=bool(meta.get("numericLike") or meta.get("numeric_like")),
|
|
439
466
|
sample_values=tuple(str(s) for s in samples) if samples else (),
|
|
440
467
|
role=str(meta.get("role") or ""),
|
|
468
|
+
encoding=_property_encoding(label_or_type, name, meta.get("encoding")),
|
|
441
469
|
)
|
|
442
470
|
elif isinstance(meta, str):
|
|
443
471
|
result[name] = PropertyInfo(field=name, type=meta)
|
|
@@ -797,6 +825,7 @@ class MappingResolver:
|
|
|
797
825
|
**({"numericLike": True} if p.numeric_like else {}),
|
|
798
826
|
**({"sampleValues": list(p.sample_values)} if p.sample_values else {}),
|
|
799
827
|
**({"role": p.role} if p.role else {}),
|
|
828
|
+
**({"encoding": p.encoding} if p.encoding else {}),
|
|
800
829
|
}
|
|
801
830
|
for name, p in props.items()
|
|
802
831
|
},
|
|
@@ -831,6 +860,7 @@ class MappingResolver:
|
|
|
831
860
|
**({"numericLike": True} if p.numeric_like else {}),
|
|
832
861
|
**({"sampleValues": list(p.sample_values)} if p.sample_values else {}),
|
|
833
862
|
**({"role": p.role} if p.role else {}),
|
|
863
|
+
**({"encoding": p.encoding} if p.encoding else {}),
|
|
834
864
|
}
|
|
835
865
|
for name, p in props.items()
|
|
836
866
|
},
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""Temporal property encodings: ``PropertyInfo.encoding``."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from arango_query_core import TEMPORAL_ENCODINGS, CoreError, MappingBundle, MappingResolver
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _resolver(properties: dict, rel_properties: dict | None = None) -> MappingResolver:
|
|
11
|
+
return MappingResolver(
|
|
12
|
+
MappingBundle(
|
|
13
|
+
conceptual_schema={"entities": [], "relationships": []},
|
|
14
|
+
physical_mapping={
|
|
15
|
+
"entities": {
|
|
16
|
+
"Post": {"collectionName": "post", "style": "COLLECTION", "properties": properties},
|
|
17
|
+
"Person": {"collectionName": "person", "style": "COLLECTION", "properties": {}},
|
|
18
|
+
},
|
|
19
|
+
"relationships": {
|
|
20
|
+
"KNOWS": {
|
|
21
|
+
"edgeCollectionName": "knows",
|
|
22
|
+
"style": "DEDICATED_COLLECTION",
|
|
23
|
+
"domain": "Person",
|
|
24
|
+
"range": "Person",
|
|
25
|
+
"properties": rel_properties or {},
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
metadata={},
|
|
30
|
+
)
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_the_vocabulary() -> None:
|
|
35
|
+
assert frozenset({"epoch_ms", "iso8601"}) == TEMPORAL_ENCODINGS
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def test_a_declared_encoding_is_read_for_entities_and_relationships() -> None:
|
|
39
|
+
resolver = _resolver(
|
|
40
|
+
{"creationDate": {"type": "datetime", "encoding": "epoch_ms"}},
|
|
41
|
+
{"creationDate": {"type": "datetime", "encoding": "iso8601"}},
|
|
42
|
+
)
|
|
43
|
+
assert resolver.resolve_properties("Post")["creationDate"].encoding == "epoch_ms"
|
|
44
|
+
assert resolver.resolve_properties("KNOWS")["creationDate"].encoding == "iso8601"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_no_declaration_means_unknown() -> None:
|
|
48
|
+
props = _resolver({"creationDate": {"type": "datetime"}, "title": "string", "id": None})
|
|
49
|
+
infos = props.resolve_properties("Post")
|
|
50
|
+
assert {name: info.encoding for name, info in infos.items()} == {
|
|
51
|
+
"creationDate": "",
|
|
52
|
+
"title": "",
|
|
53
|
+
"id": "",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@pytest.mark.parametrize("bad", ["epochMillis", "ISO8601", "seconds", 13])
|
|
58
|
+
def test_an_unknown_encoding_is_an_invalid_mapping(bad: object) -> None:
|
|
59
|
+
resolver = _resolver({"creationDate": {"type": "datetime", "encoding": bad}})
|
|
60
|
+
with pytest.raises(CoreError, match="Post.creationDate declares encoding") as err:
|
|
61
|
+
resolver.resolve_properties("Post")
|
|
62
|
+
assert err.value.code == "INVALID_MAPPING"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_the_schema_summary_carries_the_encoding_when_declared() -> None:
|
|
66
|
+
summary = _resolver(
|
|
67
|
+
{"creationDate": {"type": "datetime", "encoding": "iso8601"}, "title": {"type": "string"}},
|
|
68
|
+
{"creationDate": {"type": "datetime", "encoding": "epoch_ms"}},
|
|
69
|
+
).schema_summary()
|
|
70
|
+
post = next(e for e in summary["entities"] if e["label"] == "Post")
|
|
71
|
+
assert post["properties"]["creationDate"]["encoding"] == "iso8601"
|
|
72
|
+
assert "encoding" not in post["properties"]["title"]
|
|
73
|
+
knows = next(r for r in summary["relationships"] if r["type"] == "KNOWS")
|
|
74
|
+
assert knows["properties"]["creationDate"]["encoding"] == "epoch_ms"
|
{arango_query_core-0.2.5 → arango_query_core-0.2.6}/.cursor/rules/fix-upstream-dont-fork.mdc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{arango_query_core-0.2.5 → arango_query_core-0.2.6}/tests/test_mapping_resolver_normalization.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|