metaxy 0.0.1.dev3__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 (111) hide show
  1. metaxy/__init__.py +170 -0
  2. metaxy/_packaging.py +96 -0
  3. metaxy/_testing/__init__.py +55 -0
  4. metaxy/_testing/config.py +43 -0
  5. metaxy/_testing/metaxy_project.py +780 -0
  6. metaxy/_testing/models.py +111 -0
  7. metaxy/_testing/parametric/__init__.py +13 -0
  8. metaxy/_testing/parametric/metadata.py +664 -0
  9. metaxy/_testing/pytest_helpers.py +74 -0
  10. metaxy/_testing/runbook.py +533 -0
  11. metaxy/_utils.py +35 -0
  12. metaxy/_version.py +1 -0
  13. metaxy/cli/app.py +97 -0
  14. metaxy/cli/console.py +13 -0
  15. metaxy/cli/context.py +167 -0
  16. metaxy/cli/graph.py +610 -0
  17. metaxy/cli/graph_diff.py +290 -0
  18. metaxy/cli/list.py +46 -0
  19. metaxy/cli/metadata.py +317 -0
  20. metaxy/cli/migrations.py +999 -0
  21. metaxy/cli/utils.py +268 -0
  22. metaxy/config.py +680 -0
  23. metaxy/entrypoints.py +296 -0
  24. metaxy/ext/__init__.py +1 -0
  25. metaxy/ext/dagster/__init__.py +54 -0
  26. metaxy/ext/dagster/constants.py +10 -0
  27. metaxy/ext/dagster/dagster_type.py +156 -0
  28. metaxy/ext/dagster/io_manager.py +200 -0
  29. metaxy/ext/dagster/metaxify.py +512 -0
  30. metaxy/ext/dagster/observable.py +115 -0
  31. metaxy/ext/dagster/resources.py +27 -0
  32. metaxy/ext/dagster/selection.py +73 -0
  33. metaxy/ext/dagster/table_metadata.py +417 -0
  34. metaxy/ext/dagster/utils.py +462 -0
  35. metaxy/ext/sqlalchemy/__init__.py +23 -0
  36. metaxy/ext/sqlalchemy/config.py +29 -0
  37. metaxy/ext/sqlalchemy/plugin.py +353 -0
  38. metaxy/ext/sqlmodel/__init__.py +13 -0
  39. metaxy/ext/sqlmodel/config.py +29 -0
  40. metaxy/ext/sqlmodel/plugin.py +499 -0
  41. metaxy/graph/__init__.py +29 -0
  42. metaxy/graph/describe.py +325 -0
  43. metaxy/graph/diff/__init__.py +21 -0
  44. metaxy/graph/diff/diff_models.py +446 -0
  45. metaxy/graph/diff/differ.py +769 -0
  46. metaxy/graph/diff/models.py +443 -0
  47. metaxy/graph/diff/rendering/__init__.py +18 -0
  48. metaxy/graph/diff/rendering/base.py +323 -0
  49. metaxy/graph/diff/rendering/cards.py +188 -0
  50. metaxy/graph/diff/rendering/formatter.py +805 -0
  51. metaxy/graph/diff/rendering/graphviz.py +246 -0
  52. metaxy/graph/diff/rendering/mermaid.py +326 -0
  53. metaxy/graph/diff/rendering/rich.py +169 -0
  54. metaxy/graph/diff/rendering/theme.py +48 -0
  55. metaxy/graph/diff/traversal.py +247 -0
  56. metaxy/graph/status.py +329 -0
  57. metaxy/graph/utils.py +58 -0
  58. metaxy/metadata_store/__init__.py +32 -0
  59. metaxy/metadata_store/_ducklake_support.py +419 -0
  60. metaxy/metadata_store/base.py +1792 -0
  61. metaxy/metadata_store/bigquery.py +354 -0
  62. metaxy/metadata_store/clickhouse.py +184 -0
  63. metaxy/metadata_store/delta.py +371 -0
  64. metaxy/metadata_store/duckdb.py +446 -0
  65. metaxy/metadata_store/exceptions.py +61 -0
  66. metaxy/metadata_store/ibis.py +542 -0
  67. metaxy/metadata_store/lancedb.py +391 -0
  68. metaxy/metadata_store/memory.py +292 -0
  69. metaxy/metadata_store/system/__init__.py +57 -0
  70. metaxy/metadata_store/system/events.py +264 -0
  71. metaxy/metadata_store/system/keys.py +9 -0
  72. metaxy/metadata_store/system/models.py +129 -0
  73. metaxy/metadata_store/system/storage.py +957 -0
  74. metaxy/metadata_store/types.py +10 -0
  75. metaxy/metadata_store/utils.py +104 -0
  76. metaxy/metadata_store/warnings.py +36 -0
  77. metaxy/migrations/__init__.py +32 -0
  78. metaxy/migrations/detector.py +291 -0
  79. metaxy/migrations/executor.py +516 -0
  80. metaxy/migrations/generator.py +319 -0
  81. metaxy/migrations/loader.py +231 -0
  82. metaxy/migrations/models.py +528 -0
  83. metaxy/migrations/ops.py +447 -0
  84. metaxy/models/__init__.py +0 -0
  85. metaxy/models/bases.py +12 -0
  86. metaxy/models/constants.py +139 -0
  87. metaxy/models/feature.py +1335 -0
  88. metaxy/models/feature_spec.py +338 -0
  89. metaxy/models/field.py +263 -0
  90. metaxy/models/fields_mapping.py +307 -0
  91. metaxy/models/filter_expression.py +297 -0
  92. metaxy/models/lineage.py +285 -0
  93. metaxy/models/plan.py +232 -0
  94. metaxy/models/types.py +475 -0
  95. metaxy/py.typed +0 -0
  96. metaxy/utils/__init__.py +1 -0
  97. metaxy/utils/constants.py +2 -0
  98. metaxy/utils/exceptions.py +23 -0
  99. metaxy/utils/hashing.py +230 -0
  100. metaxy/versioning/__init__.py +31 -0
  101. metaxy/versioning/engine.py +656 -0
  102. metaxy/versioning/feature_dep_transformer.py +151 -0
  103. metaxy/versioning/ibis.py +249 -0
  104. metaxy/versioning/lineage_handler.py +205 -0
  105. metaxy/versioning/polars.py +189 -0
  106. metaxy/versioning/renamed_df.py +35 -0
  107. metaxy/versioning/types.py +63 -0
  108. metaxy-0.0.1.dev3.dist-info/METADATA +96 -0
  109. metaxy-0.0.1.dev3.dist-info/RECORD +111 -0
  110. metaxy-0.0.1.dev3.dist-info/WHEEL +4 -0
  111. metaxy-0.0.1.dev3.dist-info/entry_points.txt +4 -0
metaxy/models/types.py ADDED
@@ -0,0 +1,475 @@
1
+ """Type definitions for metaxy models."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator, Sequence
6
+ from typing import TYPE_CHECKING, Annotated, Any, NamedTuple, TypeAlias, overload
7
+
8
+ from pydantic import (
9
+ BeforeValidator,
10
+ ConfigDict,
11
+ Field,
12
+ RootModel,
13
+ TypeAdapter,
14
+ field_validator,
15
+ model_serializer,
16
+ model_validator,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from typing_extensions import Self
21
+
22
+ KEY_SEPARATOR = "/"
23
+
24
+ # backcompat
25
+ FEATURE_KEY_SEPARATOR = KEY_SEPARATOR
26
+ FIELD_KEY_SEPARATOR = KEY_SEPARATOR
27
+
28
+
29
+ class SnapshotPushResult(NamedTuple):
30
+ """Result of recording a feature graph snapshot.
31
+
32
+ Attributes:
33
+ snapshot_version: The deterministic hash of the graph snapshot
34
+ already_pushed: True if this snapshot_version was already pushed previously
35
+ updated_features: List of feature keys with updated information (changed full_definition_version)
36
+ """
37
+
38
+ snapshot_version: str
39
+ already_pushed: bool
40
+ updated_features: list[str]
41
+
42
+
43
+ _CoercibleToKey: TypeAlias = Sequence[str] | str
44
+
45
+
46
+ class _Key(RootModel[tuple[str, ...]]):
47
+ """
48
+ A common class for key-like objects that contain a sequence of string parts.
49
+
50
+ Parts cannot contain forward slashes (/) or double underscores (__).
51
+
52
+ Args:
53
+ key: Feature key as string ("a/b/c"), sequence (["a", "b", "c"]), or FeatureKey instance.
54
+ String format is split on "/" separator.
55
+ """
56
+
57
+ model_config = ConfigDict(frozen=True, repr=False) # pyright: ignore[reportCallIssue] # Make immutable for hashability, use custom __repr__
58
+
59
+ root: tuple[str, ...]
60
+
61
+ if TYPE_CHECKING:
62
+
63
+ @overload
64
+ def __init__(self, parts: str) -> None: ...
65
+
66
+ @overload
67
+ def __init__(self, parts: Sequence[str]) -> None: ...
68
+
69
+ @overload
70
+ def __init__(self, parts: Self) -> None: ...
71
+
72
+ def __init__( # pyright: ignore[reportMissingSuperCall]
73
+ self,
74
+ parts: str | Sequence[str] | Self,
75
+ ) -> None: ...
76
+
77
+ @model_validator(mode="before")
78
+ @classmethod
79
+ def _validate_input(cls, data: Any) -> Any:
80
+ """Convert various input types to tuple of strings."""
81
+ # If it's already a tuple, validate and return it
82
+ if isinstance(data, tuple):
83
+ return data
84
+
85
+ # Handle dict input (from Pydantic deserialization)
86
+ if isinstance(data, dict):
87
+ # RootModel deserialization passes dict with "root" key
88
+ if "root" in data:
89
+ root_value = data["root"]
90
+ if isinstance(root_value, tuple):
91
+ return root_value
92
+ elif isinstance(root_value, (list, Sequence)):
93
+ return tuple(root_value)
94
+ # Legacy "parts" key for backward compatibility
95
+ elif "parts" in data:
96
+ parts = data["parts"]
97
+ if (
98
+ isinstance(parts, (list, tuple))
99
+ and parts
100
+ and isinstance(parts[0], dict)
101
+ ):
102
+ # Handle incorrectly nested structure like {'parts': [{'parts': [...]}]}
103
+ if "parts" in parts[0]:
104
+ return tuple(parts[0]["parts"])
105
+ else:
106
+ raise ValueError(f"Invalid nested structure in parts: {parts}")
107
+ return tuple(parts) if not isinstance(parts, tuple) else parts
108
+ # Empty dict
109
+ raise ValueError("Dict must contain 'root' or 'parts' key")
110
+
111
+ # Handle string input - split on separator
112
+ if isinstance(data, str):
113
+ return tuple(data.split(KEY_SEPARATOR))
114
+
115
+ # Handle instance of same class - extract root
116
+ if isinstance(data, cls):
117
+ return data.root
118
+
119
+ # Handle sequence (list, etc.)
120
+ if isinstance(data, Sequence):
121
+ return tuple(data)
122
+
123
+ raise ValueError(f"Cannot create {cls.__name__} from {type(data).__name__}")
124
+
125
+ @field_validator("root", mode="after")
126
+ @classmethod
127
+ def _validate_root_content(cls, value: tuple[str, ...]) -> tuple[str, ...]:
128
+ """Validate that parts don't contain forbidden characters."""
129
+ for part in value:
130
+ if not isinstance(part, str):
131
+ raise ValueError(
132
+ f"{cls.__name__} parts must be strings, got {type(part).__name__}"
133
+ )
134
+ if "/" in part:
135
+ raise ValueError(
136
+ f"{cls.__name__} part '{part}' cannot contain forward slashes (/). "
137
+ f"Forward slashes are reserved as the separator in to_string(). "
138
+ f"Use underscores or hyphens instead."
139
+ )
140
+ if "__" in part:
141
+ raise ValueError(
142
+ f"{cls.__name__} part '{part}' cannot contain double underscores (__). "
143
+ f"Use single underscores or hyphens instead."
144
+ )
145
+ return value
146
+
147
+ @model_serializer
148
+ def _serialize_model(self) -> list[str]:
149
+ """Serialize to list format for backward compatibility."""
150
+ return list(self.parts)
151
+
152
+ @property
153
+ def parts(self) -> tuple[str, ...]:
154
+ """Backward compatibility property for accessing root as parts."""
155
+ return self.root
156
+
157
+ def to_string(self) -> str:
158
+ """Convert to string representation with "/" separator."""
159
+ return KEY_SEPARATOR.join(self.parts)
160
+
161
+ def to_struct_key(self) -> str:
162
+ """Convert to a name that can be used as struct key in databases"""
163
+ return "_".join(self.parts)
164
+
165
+ def to_column_suffix(self) -> str:
166
+ """Convert to a suffix usable for database column names (typically temporary)."""
167
+ return "__" + "_".join(self.parts)
168
+
169
+ def __repr__(self) -> str:
170
+ """Return string representation."""
171
+ return self.to_string()
172
+
173
+ def __str__(self) -> str:
174
+ """Return string representation."""
175
+ return self.to_string()
176
+
177
+ def __lt__(self, other: Any) -> bool:
178
+ """Less than comparison for sorting."""
179
+ if isinstance(other, self.__class__):
180
+ return self.parts < other.parts
181
+ return NotImplemented
182
+
183
+ def __le__(self, other: Any) -> bool:
184
+ """Less than or equal comparison for sorting."""
185
+ if isinstance(other, self.__class__):
186
+ return self.parts <= other.parts
187
+ return NotImplemented
188
+
189
+ def __gt__(self, other: Any) -> bool:
190
+ """Greater than comparison for sorting."""
191
+ if isinstance(other, self.__class__):
192
+ return self.parts > other.parts
193
+ return NotImplemented
194
+
195
+ def __ge__(self, other: Any) -> bool:
196
+ """Greater than or equal comparison for sorting."""
197
+ if isinstance(other, self.__class__):
198
+ return self.parts >= other.parts
199
+ return NotImplemented
200
+
201
+ def __iter__(self) -> Iterator[str]: # pyright: ignore[reportIncompatibleMethodOverride]
202
+ """Return iterator over parts."""
203
+ return iter(self.parts)
204
+
205
+ @property
206
+ def table_name(self) -> str:
207
+ """Get SQL-like table name for this feature key.
208
+
209
+ Replaces hyphens with underscores for SQL compatibility.
210
+ """
211
+ return "__".join(part.replace("-", "_") for part in self.parts)
212
+
213
+ # List-like interface for backward compatibility
214
+ def __getitem__(self, index: int) -> str:
215
+ """Get part by index."""
216
+ return self.parts[index]
217
+
218
+ def __len__(self) -> int:
219
+ """Get number of parts."""
220
+ return len(self.parts)
221
+
222
+ def __contains__(self, item: str) -> bool:
223
+ """Check if part is in key."""
224
+ return item in self.parts
225
+
226
+ def __reversed__(self):
227
+ """Return reversed iterator over parts."""
228
+ return reversed(self.parts)
229
+
230
+
231
+ # CoercibleToKey: TypeAlias = _CoercibleToKey | _Key
232
+
233
+
234
+ class FeatureKey(_Key):
235
+ """
236
+ Feature key as a sequence of string parts.
237
+
238
+ Hashable for use as dict keys in registries.
239
+ Parts cannot contain forward slashes (/) or double underscores (__).
240
+
241
+ Example:
242
+
243
+ ```py
244
+ FeatureKey("a/b/c") # String format
245
+ # FeatureKey(parts=['a', 'b', 'c'])
246
+
247
+ FeatureKey(["a", "b", "c"]) # List format
248
+ # FeatureKey(parts=['a', 'b', 'c'])
249
+
250
+ FeatureKey(FeatureKey(["a", "b", "c"])) # FeatureKey copy
251
+ # FeatureKey(parts=['a', 'b', 'c'])
252
+ ```
253
+ """
254
+
255
+ if TYPE_CHECKING:
256
+
257
+ @overload
258
+ def __init__(self, parts: str) -> None: ...
259
+
260
+ @overload
261
+ def __init__(self, parts: Sequence[str]) -> None: ...
262
+
263
+ @overload
264
+ def __init__(self, parts: FeatureKey) -> None: ...
265
+
266
+ def __init__( # pyright: ignore[reportMissingSuperCall]
267
+ self,
268
+ parts: str | Sequence[str] | FeatureKey,
269
+ ) -> None: ...
270
+
271
+ def model_dump(self, **kwargs: Any) -> Any:
272
+ """Serialize to list format for backward compatibility."""
273
+ # When serializing this key, return it as a list of parts
274
+ # instead of the full Pydantic model structure
275
+ return list(self.parts)
276
+
277
+ def __hash__(self) -> int:
278
+ """Return hash for use as dict keys."""
279
+ return hash(self.parts)
280
+
281
+ def __eq__(self, other: Any) -> bool:
282
+ """Check equality with another instance."""
283
+ if isinstance(other, self.__class__):
284
+ return self.parts == other.parts
285
+ return super().__eq__(other)
286
+
287
+ def to_column_suffix(self) -> str:
288
+ """Convert to a suffix usable for database column names (typically temporary)."""
289
+ return "__" + "_".join(self.parts)
290
+
291
+
292
+ class FieldKey(_Key):
293
+ """
294
+ Field key as a sequence of string parts.
295
+
296
+ Hashable for use as dict keys in registries.
297
+ Parts cannot contain forward slashes (/) or double underscores (__).
298
+
299
+ Example:
300
+
301
+ ```py
302
+ FieldKey("a/b/c") # String format
303
+ # FieldKey(parts=['a', 'b', 'c'])
304
+
305
+ FieldKey(["a", "b", "c"]) # List format
306
+ # FieldKey(parts=['a', 'b', 'c'])
307
+
308
+ FieldKey(FieldKey(["a", "b", "c"])) # FieldKey copy
309
+ # FieldKey(parts=['a', 'b', 'c'])
310
+ ```
311
+ """
312
+
313
+ if TYPE_CHECKING:
314
+
315
+ @overload
316
+ def __init__(self, parts: str) -> None: ...
317
+
318
+ @overload
319
+ def __init__(self, parts: Sequence[str]) -> None: ...
320
+
321
+ @overload
322
+ def __init__(self, parts: FieldKey) -> None: ...
323
+
324
+ def __init__( # pyright: ignore[reportMissingSuperCall]
325
+ self,
326
+ parts: str | Sequence[str] | FieldKey,
327
+ ) -> None: ...
328
+
329
+ def model_dump(self, **kwargs: Any) -> Any:
330
+ """Serialize to list format for backward compatibility."""
331
+ # When serializing this key, return it as a list of parts
332
+ # instead of the full Pydantic model structure
333
+ return list(self.parts)
334
+
335
+ def __hash__(self) -> int:
336
+ """Return hash for use as dict keys."""
337
+ return hash(self.parts)
338
+
339
+ def __eq__(self, other: Any) -> bool:
340
+ """Check equality with another instance."""
341
+ if isinstance(other, self.__class__):
342
+ return self.parts == other.parts
343
+ return super().__eq__(other)
344
+
345
+
346
+ _CoercibleToFeatureKey: TypeAlias = _CoercibleToKey | FeatureKey
347
+
348
+ FeatureKeyAdapter = TypeAdapter(
349
+ FeatureKey
350
+ ) # can call .validate_python() to transform acceptable types into a FeatureKey
351
+ FieldKeyAdapter = TypeAdapter(
352
+ FieldKey
353
+ ) # can call .validate_python() to transform acceptable types into a FieldKey
354
+
355
+
356
+ def _coerce_to_feature_key(value: Any) -> FeatureKey:
357
+ """Convert various types to FeatureKey.
358
+
359
+ Accepts:
360
+
361
+ - slashed `str`: `"a/b/c"`
362
+
363
+ - `Sequence[str]`: `["a", "b", "c"]`
364
+
365
+ - `FeatureKey`: pass through
366
+
367
+ - `type[BaseFeature]`: extracts .spec().key
368
+
369
+ Args:
370
+ value: Value to coerce to `FeatureKey`
371
+
372
+ Returns:
373
+ canonical `FeatureKey` instance
374
+
375
+ Raises:
376
+ ValidationError: If value cannot be coerced to FeatureKey
377
+ """
378
+ if isinstance(value, FeatureKey):
379
+ return value
380
+
381
+ # Check if it's a BaseFeature class
382
+ # Import here to avoid circular dependency at module level
383
+ from metaxy.models.feature import BaseFeature
384
+
385
+ if isinstance(value, type) and issubclass(value, BaseFeature):
386
+ return value.spec().key
387
+
388
+ # Handle str, Sequence[str]
389
+ return FeatureKeyAdapter.validate_python(value)
390
+
391
+
392
+ def _coerce_to_field_key(value: Any) -> FieldKey:
393
+ """Convert various types to FieldKey.
394
+
395
+ Accepts:
396
+
397
+ - slashed `str`: `"a/b/c"`
398
+
399
+ - `Sequence[str]`: `["a", "b", "c"]`
400
+
401
+ - `FieldKey`: pass through
402
+
403
+ Args:
404
+ value: Value to coerce to `FieldKey`
405
+
406
+ Returns:
407
+ canonical `FieldKey` instance
408
+
409
+ Raises:
410
+ ValidationError: If value cannot be coerced to `FieldKey`
411
+ """
412
+ if isinstance(value, FieldKey):
413
+ return value
414
+
415
+ # Handle str, Sequence[str]
416
+ return FieldKeyAdapter.validate_python(value)
417
+
418
+
419
+ if TYPE_CHECKING:
420
+ from metaxy.models.feature import BaseFeature
421
+
422
+ # Type unions - what inputs are accepted
423
+ CoercibleToFeatureKey: TypeAlias = (
424
+ str | Sequence[str] | FeatureKey | type["BaseFeature"]
425
+ )
426
+ CoercibleToFieldKey: TypeAlias = str | Sequence[str] | FieldKey
427
+
428
+ # Annotated types for Pydantic field annotations - automatically validate
429
+ # After validation, these ARE FeatureKey/FieldKey (not unions)
430
+ ValidatedFeatureKey: TypeAlias = Annotated[
431
+ FeatureKey,
432
+ BeforeValidator(_coerce_to_feature_key),
433
+ Field(
434
+ description="Feature key. Accepts a slashed string ('a/b/c'), a sequence of strings, a FeatureKey instance, or a child class of BaseFeature"
435
+ ),
436
+ ]
437
+
438
+ ValidatedFieldKey: TypeAlias = Annotated[
439
+ FieldKey,
440
+ BeforeValidator(_coerce_to_field_key),
441
+ Field(
442
+ description="Field key. Accepts a slashed string ('a/b/c'), a sequence of strings, or a FieldKey instance."
443
+ ),
444
+ ]
445
+
446
+ # TypeAdapters for non-Pydantic usage (e.g., in metadata_store/base.py)
447
+ ValidatedFeatureKeyAdapter: TypeAdapter[ValidatedFeatureKey] = TypeAdapter(
448
+ ValidatedFeatureKey
449
+ )
450
+ ValidatedFieldKeyAdapter: TypeAdapter[ValidatedFieldKey] = TypeAdapter(
451
+ ValidatedFieldKey
452
+ )
453
+
454
+
455
+ # Collection types for common patterns - automatically validate sequences
456
+ # Pydantic will validate each element using ValidatedFeatureKey/ValidatedFieldKey
457
+ ValidatedFeatureKeySequence: TypeAlias = Annotated[
458
+ Sequence[ValidatedFeatureKey],
459
+ Field(description="Sequence items coerced into FeatureKey."),
460
+ ]
461
+
462
+ ValidatedFieldKeySequence: TypeAlias = Annotated[
463
+ Sequence[ValidatedFieldKey],
464
+ Field(description="Sequence items coerced into FieldKey."),
465
+ ]
466
+
467
+ # TypeAdapters for non-Pydantic usage
468
+ ValidatedFeatureKeySequenceAdapter: TypeAdapter[ValidatedFeatureKeySequence] = (
469
+ TypeAdapter(ValidatedFeatureKeySequence)
470
+ )
471
+ ValidatedFieldKeySequenceAdapter: TypeAdapter[ValidatedFieldKeySequence] = TypeAdapter(
472
+ ValidatedFieldKeySequence
473
+ )
474
+
475
+ FeatureDepMetadata: TypeAlias = dict[str, Any]
metaxy/py.typed ADDED
File without changes
@@ -0,0 +1 @@
1
+ """Utility modules for Metaxy."""
@@ -0,0 +1,2 @@
1
+ # Sentinel value for code_version in struct serialization
2
+ DEFAULT_CODE_VERSION = "metaxy__intitial"
@@ -0,0 +1,23 @@
1
+ class MetaxyError(Exception):
2
+ """Base class for all errors thrown by the Metaxy framework.
3
+
4
+ Users should not subclass this base class for their own exceptions.
5
+ """
6
+
7
+ @property
8
+ def is_user_code_error(self):
9
+ """Returns true if this error is attributable to user code."""
10
+ return False
11
+
12
+
13
+ class MetaxyInvariantViolationError(MetaxyError):
14
+ """Indicates the user has violated a well-defined invariant that can only be enforced
15
+ at runtime.
16
+ """
17
+
18
+
19
+ class MetaxyEmptyCodeVersionError(MetaxyInvariantViolationError):
20
+ """Indicates that an empty code version was provided where it is not allowed.
21
+
22
+ Code version must be a non-empty string.
23
+ """