lorebase-core 0.2.3__py3-none-any.whl

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 (94) hide show
  1. lorebase_core/__init__.py +1 -0
  2. lorebase_core/canonical/__init__.py +35 -0
  3. lorebase_core/canonical/hashing.py +128 -0
  4. lorebase_core/canonical/jcs.py +162 -0
  5. lorebase_core/expr/__init__.py +118 -0
  6. lorebase_core/expr/ast.py +443 -0
  7. lorebase_core/expr/checker.py +428 -0
  8. lorebase_core/expr/compiler.py +87 -0
  9. lorebase_core/expr/context.py +82 -0
  10. lorebase_core/expr/derivations.py +306 -0
  11. lorebase_core/expr/environment.py +176 -0
  12. lorebase_core/expr/errors.py +98 -0
  13. lorebase_core/expr/evaluator.py +414 -0
  14. lorebase_core/expr/functions.py +898 -0
  15. lorebase_core/expr/limits.py +52 -0
  16. lorebase_core/expr/operations.py +479 -0
  17. lorebase_core/expr/ops.py +243 -0
  18. lorebase_core/expr/resolver.py +96 -0
  19. lorebase_core/expr/rows.py +148 -0
  20. lorebase_core/expr/sensitivity.py +337 -0
  21. lorebase_core/expr/types.py +227 -0
  22. lorebase_core/expr/validation.py +51 -0
  23. lorebase_core/expr/values.py +169 -0
  24. lorebase_core/expr/visibility.py +363 -0
  25. lorebase_core/infer/__init__.py +36 -0
  26. lorebase_core/infer/columns.py +621 -0
  27. lorebase_core/infer/engine.py +546 -0
  28. lorebase_core/infer/keys.py +164 -0
  29. lorebase_core/infer/names.py +145 -0
  30. lorebase_core/infer/options.py +290 -0
  31. lorebase_core/infer/profile.py +54 -0
  32. lorebase_core/infer/proposal.py +122 -0
  33. lorebase_core/infer/references.py +314 -0
  34. lorebase_core/infer/suggestions.py +731 -0
  35. lorebase_core/infer/tables.py +81 -0
  36. lorebase_core/merge/__init__.py +89 -0
  37. lorebase_core/merge/conflicts.py +262 -0
  38. lorebase_core/merge/consistency.py +440 -0
  39. lorebase_core/merge/data_merge.py +522 -0
  40. lorebase_core/merge/engine.py +476 -0
  41. lorebase_core/merge/index.py +100 -0
  42. lorebase_core/merge/objects.py +204 -0
  43. lorebase_core/merge/policies.py +322 -0
  44. lorebase_core/merge/resolve.py +321 -0
  45. lorebase_core/merge/schema_merge.py +429 -0
  46. lorebase_core/merge/state.py +443 -0
  47. lorebase_core/model/__init__.py +245 -0
  48. lorebase_core/model/attribute.py +72 -0
  49. lorebase_core/model/base.py +61 -0
  50. lorebase_core/model/derivation.py +95 -0
  51. lorebase_core/model/domain.py +71 -0
  52. lorebase_core/model/embedding.py +193 -0
  53. lorebase_core/model/ids.py +78 -0
  54. lorebase_core/model/kind.py +85 -0
  55. lorebase_core/model/lineage.py +114 -0
  56. lorebase_core/model/operation.py +161 -0
  57. lorebase_core/model/paths.py +141 -0
  58. lorebase_core/model/record.py +127 -0
  59. lorebase_core/model/relation.py +83 -0
  60. lorebase_core/model/schema.py +219 -0
  61. lorebase_core/model/source.py +204 -0
  62. lorebase_core/model/trait.py +37 -0
  63. lorebase_core/model/vault.py +43 -0
  64. lorebase_core/model/yaml_schema.py +1058 -0
  65. lorebase_core/query/__init__.py +97 -0
  66. lorebase_core/query/dsl.py +227 -0
  67. lorebase_core/query/resolve.py +794 -0
  68. lorebase_core/revision/__init__.py +59 -0
  69. lorebase_core/revision/ancestry.py +272 -0
  70. lorebase_core/revision/changeset.py +62 -0
  71. lorebase_core/revision/revision.py +121 -0
  72. lorebase_core/tree/__init__.py +76 -0
  73. lorebase_core/tree/diff.py +119 -0
  74. lorebase_core/tree/hamt.py +333 -0
  75. lorebase_core/tree/node.py +280 -0
  76. lorebase_core/tree/params.py +33 -0
  77. lorebase_core/tree/root.py +70 -0
  78. lorebase_core/tree/store.py +98 -0
  79. lorebase_core/types/__init__.py +71 -0
  80. lorebase_core/types/casts.py +271 -0
  81. lorebase_core/types/constraints.py +299 -0
  82. lorebase_core/types/encoding.py +538 -0
  83. lorebase_core/types/errors.py +41 -0
  84. lorebase_core/types/matching.py +40 -0
  85. lorebase_core/types/types.py +187 -0
  86. lorebase_core/validate/__init__.py +49 -0
  87. lorebase_core/validate/changes.py +70 -0
  88. lorebase_core/validate/changeset.py +551 -0
  89. lorebase_core/validate/data_rules.py +291 -0
  90. lorebase_core/validate/issues.py +123 -0
  91. lorebase_core/validate/schema_rules.py +1304 -0
  92. lorebase_core-0.2.3.dist-info/METADATA +9 -0
  93. lorebase_core-0.2.3.dist-info/RECORD +94 -0
  94. lorebase_core-0.2.3.dist-info/WHEEL +4 -0
@@ -0,0 +1,97 @@
1
+ """Query DSL (design 8.2): request models and their validation against a Schema."""
2
+
3
+ from lorebase_core.query.dsl import (
4
+ Aggregate,
5
+ AllOf,
6
+ AnyOf,
7
+ AttributeCondition,
8
+ CountCondition,
9
+ DerivedCondition,
10
+ Include,
11
+ MatchCondition,
12
+ NotCondition,
13
+ Operator,
14
+ OrderBy,
15
+ Predicate,
16
+ Query,
17
+ ViaCondition,
18
+ )
19
+ from lorebase_core.query.resolve import (
20
+ KEY_FIELD,
21
+ OPERATORS,
22
+ TITLE_FIELD,
23
+ AggregatePlan,
24
+ Comparison,
25
+ Condition,
26
+ Conjunction,
27
+ Disjunction,
28
+ FieldRef,
29
+ FieldSource,
30
+ FullText,
31
+ Hop,
32
+ IncludePlan,
33
+ LinkCount,
34
+ LinkExists,
35
+ Negation,
36
+ Ordering,
37
+ QueryError,
38
+ QueryIssue,
39
+ QueryLimits,
40
+ QueryPlan,
41
+ Target,
42
+ condition_kinds,
43
+ plan_kinds,
44
+ resolve_field,
45
+ resolve_hop,
46
+ resolve_query,
47
+ resolve_where,
48
+ target_of_kind,
49
+ target_of_trait,
50
+ )
51
+
52
+ __all__ = [
53
+ "KEY_FIELD",
54
+ "OPERATORS",
55
+ "TITLE_FIELD",
56
+ "Aggregate",
57
+ "AggregatePlan",
58
+ "AllOf",
59
+ "AnyOf",
60
+ "AttributeCondition",
61
+ "Comparison",
62
+ "Condition",
63
+ "Conjunction",
64
+ "CountCondition",
65
+ "DerivedCondition",
66
+ "Disjunction",
67
+ "FieldRef",
68
+ "FieldSource",
69
+ "FullText",
70
+ "Hop",
71
+ "Include",
72
+ "IncludePlan",
73
+ "LinkCount",
74
+ "LinkExists",
75
+ "MatchCondition",
76
+ "Negation",
77
+ "NotCondition",
78
+ "Operator",
79
+ "OrderBy",
80
+ "Ordering",
81
+ "Predicate",
82
+ "Query",
83
+ "QueryError",
84
+ "QueryIssue",
85
+ "QueryLimits",
86
+ "QueryPlan",
87
+ "Target",
88
+ "ViaCondition",
89
+ "condition_kinds",
90
+ "plan_kinds",
91
+ "resolve_field",
92
+ "resolve_hop",
93
+ "resolve_query",
94
+ "resolve_where",
95
+ "target_of_kind",
96
+ "target_of_trait",
97
+ ]
@@ -0,0 +1,227 @@
1
+ """The JSON query DSL (design 8.2): request models only, no schema knowledge.
2
+
3
+ A query names a Kind (or a Trait, which expands to the Kinds implementing it), an optional
4
+ `where` predicate, the fields to return, Links to include, the ordering and a page size:
5
+
6
+ {
7
+ "kind": "Part",
8
+ "where": {"and": [
9
+ {"attr": "category", "op": "eq", "value": "bearing"},
10
+ {"via": "supplied_by", "where": {"attr": "country", "op": "in", "value": ["IT", "DE"]}},
11
+ {"count": "supplied_by", "op": "gte", "value": 2}
12
+ ]},
13
+ "select": ["key", "title", "category"],
14
+ "include": {"supplied_by": {"select": ["key", "title"], "limit": 5}},
15
+ "order_by": [{"attr": "category"}, {"attr": "key", "dir": "desc"}],
16
+ "limit": 50,
17
+ "aggregate": {"group_by": "category"}
18
+ }
19
+
20
+ Predicates are JSON objects told apart by their leading member:
21
+
22
+ - `{"and": [...]}`, `{"or": [...]}`, `{"not": {...}}`: boolean composition;
23
+ - `{"attr": name, "op": op, "value": v}`: compare an Attribute (or the pseudo fields `key` and
24
+ `title`); `{"derived": name, ...}` does the same for a derived Attribute;
25
+ - `{"via": relation, "where": {...}}`: a Link of the Relation (forward or inverse api name)
26
+ exists, optionally to a Record matching `where`;
27
+ - `{"count": relation, "op": op, "value": n, "where": {...}}`: compare the number of such Links;
28
+ - `{"match": "text"}`: full-text match on the Record's search vector.
29
+
30
+ Validation against a Schema lives in `lorebase_core.query.resolve`.
31
+ """
32
+
33
+ from __future__ import annotations
34
+
35
+ from enum import StrEnum
36
+ from typing import Annotated, Any, Literal, Self
37
+
38
+ from pydantic import (
39
+ BaseModel,
40
+ ConfigDict,
41
+ Discriminator,
42
+ Field,
43
+ JsonValue,
44
+ Tag,
45
+ model_validator,
46
+ )
47
+
48
+ __all__ = [
49
+ "Aggregate",
50
+ "AllOf",
51
+ "AnyOf",
52
+ "AttributeCondition",
53
+ "CountCondition",
54
+ "DerivedCondition",
55
+ "Include",
56
+ "MatchCondition",
57
+ "NotCondition",
58
+ "Operator",
59
+ "OrderBy",
60
+ "Predicate",
61
+ "Query",
62
+ "ViaCondition",
63
+ ]
64
+
65
+
66
+ class QueryModel(BaseModel):
67
+ """Frozen request model; unknown members are rejected."""
68
+
69
+ model_config = ConfigDict(frozen=True, extra="forbid", populate_by_name=True)
70
+
71
+
72
+ class Operator(StrEnum):
73
+ """Comparison operators; which ones apply depends on the Attribute type."""
74
+
75
+ EQ = "eq"
76
+ NE = "ne"
77
+ LT = "lt"
78
+ LTE = "lte"
79
+ GT = "gt"
80
+ GTE = "gte"
81
+ IN = "in"
82
+ BETWEEN = "between"
83
+ CONTAINS = "contains"
84
+ PREFIX = "prefix"
85
+ EXISTS = "exists"
86
+ MATCH = "match"
87
+ NEAR = "near"
88
+
89
+
90
+ class AttributeCondition(QueryModel):
91
+ """Compare an Attribute, `key` or `title` with a value."""
92
+
93
+ attr: str = Field(min_length=1)
94
+ op: Operator
95
+ value: JsonValue = None
96
+
97
+
98
+ class DerivedCondition(QueryModel):
99
+ """Compare a derived Attribute with a value."""
100
+
101
+ derived: str = Field(min_length=1)
102
+ op: Operator
103
+ value: JsonValue = None
104
+
105
+
106
+ class ViaCondition(QueryModel):
107
+ """A Link of `via` exists, to a Record matching `where` when given."""
108
+
109
+ via: str = Field(min_length=1)
110
+ where: Predicate | None = None
111
+
112
+
113
+ class CountCondition(QueryModel):
114
+ """Compare the number of Links of `count` (to Records matching `where`) with a value."""
115
+
116
+ count: str = Field(min_length=1)
117
+ op: Operator
118
+ value: JsonValue
119
+ where: Predicate | None = None
120
+
121
+
122
+ class MatchCondition(QueryModel):
123
+ """Full-text match of the Record (title and searchable Attributes)."""
124
+
125
+ match: str = Field(min_length=1)
126
+
127
+
128
+ class AllOf(QueryModel):
129
+ items: list[Predicate] = Field(alias="and", min_length=1)
130
+
131
+
132
+ class AnyOf(QueryModel):
133
+ items: list[Predicate] = Field(alias="or", min_length=1)
134
+
135
+
136
+ class NotCondition(QueryModel):
137
+ item: Predicate = Field(alias="not")
138
+
139
+
140
+ _TAGS = ("and", "or", "not", "via", "count", "match", "derived", "attr")
141
+ _TAG_OF_MODEL: dict[type[QueryModel], str] = {}
142
+
143
+
144
+ def _predicate_tag(value: Any) -> str | None:
145
+ """Discriminate predicates by their leading member (raw JSON) or by model class."""
146
+ if isinstance(value, dict):
147
+ for tag in _TAGS:
148
+ if tag in value:
149
+ return tag
150
+ return None
151
+ return _TAG_OF_MODEL.get(type(value))
152
+
153
+
154
+ Predicate = Annotated[
155
+ Annotated[AllOf, Tag("and")]
156
+ | Annotated[AnyOf, Tag("or")]
157
+ | Annotated[NotCondition, Tag("not")]
158
+ | Annotated[ViaCondition, Tag("via")]
159
+ | Annotated[CountCondition, Tag("count")]
160
+ | Annotated[MatchCondition, Tag("match")]
161
+ | Annotated[DerivedCondition, Tag("derived")]
162
+ | Annotated[AttributeCondition, Tag("attr")],
163
+ Discriminator(
164
+ _predicate_tag,
165
+ custom_error_type="predicate",
166
+ custom_error_message="a predicate needs one of the members " + ", ".join(_TAGS),
167
+ ),
168
+ ]
169
+ """Any predicate of the DSL."""
170
+
171
+ _TAG_OF_MODEL.update(
172
+ {
173
+ AllOf: "and",
174
+ AnyOf: "or",
175
+ NotCondition: "not",
176
+ ViaCondition: "via",
177
+ CountCondition: "count",
178
+ MatchCondition: "match",
179
+ DerivedCondition: "derived",
180
+ AttributeCondition: "attr",
181
+ }
182
+ )
183
+
184
+
185
+ class OrderBy(QueryModel):
186
+ """One ordering term: an Attribute, `key` or `title`. Absent values sort last."""
187
+
188
+ attr: str = Field(min_length=1)
189
+ dir: Literal["asc", "desc"] = "asc"
190
+
191
+
192
+ class Include(QueryModel):
193
+ """Neighbors to return with each Record, through one Relation."""
194
+
195
+ select: list[str] | None = None
196
+ limit: int | None = Field(default=None, ge=1)
197
+ where: Predicate | None = None
198
+
199
+
200
+ class Aggregate(QueryModel):
201
+ """Count the matching Records per Kind, and per value of `group_by` when given."""
202
+
203
+ group_by: str | None = None
204
+
205
+
206
+ class Query(QueryModel):
207
+ """A query over the Records of one Kind or of the Kinds implementing a Trait."""
208
+
209
+ kind: str | None = None
210
+ trait: str | None = None
211
+ where: Predicate | None = None
212
+ select: list[str] | None = None
213
+ include: dict[str, Include] = Field(default_factory=dict)
214
+ order_by: list[OrderBy] = Field(default_factory=list)
215
+ limit: int | None = Field(default=None, ge=0)
216
+ cursor: str | None = None
217
+ aggregate: Aggregate | None = None
218
+
219
+ @model_validator(mode="after")
220
+ def _one_target(self) -> Self:
221
+ if (self.kind is None) == (self.trait is None):
222
+ raise ValueError("a query names exactly one of kind or trait")
223
+ return self
224
+
225
+
226
+ for _model in (ViaCondition, CountCondition, AllOf, AnyOf, NotCondition, Include, Query):
227
+ _model.model_rebuild()