kitedb 0.2.6__cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.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.
kitedb/schema.py ADDED
@@ -0,0 +1,327 @@
1
+ """
2
+ Schema Definition API for KiteDB
3
+
4
+ Provides type-safe schema builders for defining graph nodes and edges.
5
+
6
+ Example:
7
+ >>> from kitedb import node, edge, prop, optional
8
+ >>>
9
+ >>> user = node("user",
10
+ ... key=lambda id: f"user:{id}",
11
+ ... props={
12
+ ... "name": prop.string("name"),
13
+ ... "email": prop.string("email"),
14
+ ... "age": optional(prop.int("age")),
15
+ ... }
16
+ ... )
17
+ >>>
18
+ >>> knows = edge("knows", {
19
+ ... "since": prop.int("since"),
20
+ ... })
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from dataclasses import dataclass, field
26
+ from typing import (
27
+ Any,
28
+ Callable,
29
+ Dict,
30
+ Generic,
31
+ Literal,
32
+ Optional,
33
+ TypeVar,
34
+ Union,
35
+ overload,
36
+ )
37
+
38
+ # ============================================================================
39
+ # Property Type System
40
+ # ============================================================================
41
+
42
+ PropType = Literal["string", "int", "float", "bool", "vector"]
43
+
44
+ # Type variable for property values
45
+ T = TypeVar("T")
46
+ KeyArg = TypeVar("KeyArg", str, int)
47
+
48
+
49
+ @dataclass(frozen=True)
50
+ class PropDef(Generic[T]):
51
+ """
52
+ A property definition with type information.
53
+
54
+ This holds metadata about a property (name, type, whether it's optional).
55
+ The generic parameter T represents the Python type this property maps to:
56
+ - "string" -> str
57
+ - "int" -> int
58
+ - "float" -> float
59
+ - "bool" -> bool
60
+ - "vector" -> list[float]
61
+ """
62
+ name: str
63
+ type: PropType
64
+ optional: bool = False
65
+
66
+ def make_optional(self) -> PropDef[T]:
67
+ """Convert this property to an optional property."""
68
+ return PropDef(name=self.name, type=self.type, optional=True)
69
+
70
+
71
+ class PropBuilder:
72
+ """
73
+ Property type builders.
74
+
75
+ Use these to define typed properties on nodes and edges.
76
+
77
+ Example:
78
+ >>> name = prop.string("name")
79
+ >>> age = prop.int("age")
80
+ >>> score = optional(prop.float("score"))
81
+ >>> active = prop.bool("active")
82
+ """
83
+
84
+ @staticmethod
85
+ def string(name: str) -> PropDef[str]:
86
+ """
87
+ String property.
88
+ Stored as UTF-8 strings.
89
+ """
90
+ return PropDef(name=name, type="string")
91
+
92
+ @staticmethod
93
+ def int(name: str) -> PropDef[int]:
94
+ """
95
+ Integer property.
96
+ Stored as 64-bit signed integers.
97
+ """
98
+ return PropDef(name=name, type="int")
99
+
100
+ @staticmethod
101
+ def float(name: str) -> PropDef[float]:
102
+ """
103
+ Float property.
104
+ Stored as 64-bit IEEE 754 floats.
105
+ """
106
+ return PropDef(name=name, type="float")
107
+
108
+ @staticmethod
109
+ def bool(name: str) -> PropDef[bool]:
110
+ """
111
+ Boolean property.
112
+ """
113
+ return PropDef(name=name, type="bool")
114
+
115
+ @staticmethod
116
+ def vector(name: str) -> PropDef[list[float]]:
117
+ """
118
+ Vector property for embeddings.
119
+ Stored as float32 arrays.
120
+ """
121
+ return PropDef(name=name, type="vector")
122
+
123
+
124
+ # Global prop builder instance
125
+ prop = PropBuilder()
126
+
127
+
128
+ def optional(p: PropDef[T]) -> PropDef[T]:
129
+ """
130
+ Helper to make a property optional.
131
+
132
+ Example:
133
+ >>> age = optional(prop.int("age"))
134
+ """
135
+ return p.make_optional()
136
+
137
+
138
+ # ============================================================================
139
+ # Node Definition
140
+ # ============================================================================
141
+
142
+ # Type for property schemas
143
+ PropsSchema = Dict[str, PropDef[Any]]
144
+
145
+
146
+ @dataclass
147
+ class NodeDef(Generic[KeyArg]):
148
+ """
149
+ A defined node type with metadata.
150
+
151
+ Created by `node()` and used throughout the API.
152
+
153
+ Attributes:
154
+ name: The node type name (must be unique per schema)
155
+ key_fn: Function to transform application IDs to full node keys
156
+ props: Property definitions for this node type
157
+ """
158
+ name: str
159
+ key_fn: Callable[[KeyArg], str]
160
+ props: PropsSchema
161
+ # Internal: resolved prop key IDs (set during db initialization)
162
+ _prop_key_ids: Dict[str, int] = field(default_factory=dict, repr=False)
163
+
164
+ def __hash__(self) -> int:
165
+ return hash(self.name)
166
+
167
+ def __eq__(self, other: object) -> bool:
168
+ if isinstance(other, NodeDef):
169
+ return self.name == other.name
170
+ return False
171
+
172
+
173
+ def node(
174
+ name: str,
175
+ *,
176
+ key: Callable[[KeyArg], str],
177
+ props: PropsSchema,
178
+ ) -> NodeDef[KeyArg]:
179
+ """
180
+ Define a node type with properties.
181
+
182
+ Creates a node definition that can be used for all node operations
183
+ (insert, update, delete, query). Provides full type inference for
184
+ insert values and return types.
185
+
186
+ Args:
187
+ name: The node type name (must be unique)
188
+ key: Function to generate full keys from application IDs
189
+ props: Property definitions using `prop.*` builders
190
+
191
+ Returns:
192
+ A NodeDef that can be used with the database API
193
+
194
+ Example:
195
+ >>> user = node("user",
196
+ ... key=lambda id: f"user:{id}",
197
+ ... props={
198
+ ... "name": prop.string("name"),
199
+ ... "email": prop.string("email"),
200
+ ... "age": optional(prop.int("age")),
201
+ ... }
202
+ ... )
203
+ """
204
+ return NodeDef(name=name, key_fn=key, props=props)
205
+
206
+
207
+ # Backwards compatibility alias
208
+ define_node = node
209
+
210
+
211
+ # ============================================================================
212
+ # Edge Definition
213
+ # ============================================================================
214
+
215
+ @dataclass
216
+ class EdgeDef:
217
+ """
218
+ A defined edge type with metadata.
219
+
220
+ Created by `edge()` and used throughout the API.
221
+
222
+ Attributes:
223
+ name: The edge type name (must be unique per schema)
224
+ props: Property definitions for this edge type
225
+ """
226
+ name: str
227
+ props: PropsSchema = field(default_factory=dict)
228
+ # Internal: resolved edge type ID (set during db initialization)
229
+ _etype_id: Optional[int] = field(default=None, repr=False)
230
+ # Internal: resolved prop key IDs (set during db initialization)
231
+ _prop_key_ids: Dict[str, int] = field(default_factory=dict, repr=False)
232
+
233
+ def __hash__(self) -> int:
234
+ return hash(self.name)
235
+
236
+ def __eq__(self, other: object) -> bool:
237
+ if isinstance(other, EdgeDef):
238
+ return self.name == other.name
239
+ return False
240
+
241
+
242
+ @overload
243
+ def edge(name: str) -> EdgeDef: ...
244
+
245
+ @overload
246
+ def edge(name: str, props: PropsSchema) -> EdgeDef: ...
247
+
248
+ def edge(name: str, props: Optional[PropsSchema] = None) -> EdgeDef:
249
+ """
250
+ Define an edge type with optional properties.
251
+
252
+ Creates an edge definition that can be used for all edge operations
253
+ (link, unlink, query). Edges are directional and can have properties.
254
+
255
+ Args:
256
+ name: The edge type name (must be unique)
257
+ props: Optional property definitions using `prop.*` builders
258
+
259
+ Returns:
260
+ An EdgeDef that can be used with the database API
261
+
262
+ Example:
263
+ >>> # Edge with properties
264
+ >>> knows = edge("knows", {
265
+ ... "since": prop.int("since"),
266
+ ... "weight": optional(prop.float("weight")),
267
+ ... })
268
+ >>>
269
+ >>> # Edge without properties
270
+ >>> follows = edge("follows")
271
+ """
272
+ return EdgeDef(name=name, props=props or {})
273
+
274
+
275
+ # Backwards compatibility alias
276
+ define_edge = edge
277
+
278
+
279
+ # ============================================================================
280
+ # Type Inference Helpers (for documentation and type checkers)
281
+ # ============================================================================
282
+
283
+ # These TypedDict-style types help type checkers understand the shape
284
+ # of data passed to/from the API. In practice, we use runtime dicts
285
+ # but these provide IDE completion and type checking.
286
+
287
+ from typing import TypedDict
288
+
289
+
290
+ class NodeRefBase(TypedDict, total=False):
291
+ """Base type for node references."""
292
+ pass
293
+
294
+
295
+ class NodeRef(TypedDict):
296
+ """
297
+ A node reference with its ID and key.
298
+
299
+ All returned nodes have these system fields plus their property fields.
300
+ """
301
+ id: int # Internal node ID
302
+ key: str # Full node key (e.g., "user:alice")
303
+
304
+
305
+ # For dynamic property types, we can't use TypedDict directly
306
+ # but we provide runtime type checking and IDE support through
307
+ # the builder pattern.
308
+
309
+
310
+ __all__ = [
311
+ # Property builders
312
+ "prop",
313
+ "PropDef",
314
+ "PropBuilder",
315
+ "optional",
316
+ # Node definitions
317
+ "NodeDef",
318
+ "node",
319
+ "define_node", # backwards compat
320
+ # Edge definitions
321
+ "EdgeDef",
322
+ "edge",
323
+ "define_edge", # backwards compat
324
+ # Type helpers
325
+ "PropsSchema",
326
+ "NodeRef",
327
+ ]