python-datamodel 0.10.1__cp311-cp311-win32.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 (78) hide show
  1. datamodel/__init__.py +13 -0
  2. datamodel/abstract.py +383 -0
  3. datamodel/adaptive/__init__.py +0 -0
  4. datamodel/adaptive/models.py +598 -0
  5. datamodel/aliases/__init__.py +26 -0
  6. datamodel/base.py +180 -0
  7. datamodel/converters.c +43471 -0
  8. datamodel/converters.cp311-win32.pyd +0 -0
  9. datamodel/converters.html +17387 -0
  10. datamodel/converters.pyx +1489 -0
  11. datamodel/exceptions.c +13455 -0
  12. datamodel/exceptions.cp311-win32.pyd +0 -0
  13. datamodel/exceptions.html +1261 -0
  14. datamodel/exceptions.pxd +13 -0
  15. datamodel/exceptions.pyx +50 -0
  16. datamodel/fields.cp311-win32.pyd +0 -0
  17. datamodel/fields.cpp +17401 -0
  18. datamodel/fields.html +3912 -0
  19. datamodel/fields.pyx +309 -0
  20. datamodel/functions.cp311-win32.pyd +0 -0
  21. datamodel/functions.cpp +9068 -0
  22. datamodel/functions.html +1766 -0
  23. datamodel/functions.pxd +9 -0
  24. datamodel/functions.pyx +82 -0
  25. datamodel/jsonld/__init__.py +45 -0
  26. datamodel/jsonld/models.py +500 -0
  27. datamodel/libs/__init__.py +1 -0
  28. datamodel/libs/mapping.c +15067 -0
  29. datamodel/libs/mapping.cp311-win32.pyd +0 -0
  30. datamodel/libs/mapping.html +2618 -0
  31. datamodel/libs/mapping.pxd +11 -0
  32. datamodel/libs/mapping.pyx +135 -0
  33. datamodel/libs/mutables.py +127 -0
  34. datamodel/models.py +814 -0
  35. datamodel/parsers/__init__.py +0 -0
  36. datamodel/parsers/encoders.py +15 -0
  37. datamodel/parsers/json.cp311-win32.pyd +0 -0
  38. datamodel/parsers/json.cpp +17004 -0
  39. datamodel/parsers/json.html +3365 -0
  40. datamodel/parsers/json.pyx +250 -0
  41. datamodel/profiler.py +21 -0
  42. datamodel/py.typed +0 -0
  43. datamodel/rs_core/Cargo.toml +17 -0
  44. datamodel/rs_core/src/lib.rs +294 -0
  45. datamodel/rs_parsers/Cargo.toml +22 -0
  46. datamodel/rs_parsers/src/lib.rs +571 -0
  47. datamodel/rs_parsers.cp311-win32.pyd +0 -0
  48. datamodel/rs_validators/Cargo.toml +17 -0
  49. datamodel/rs_validators/src/lib.rs +0 -0
  50. datamodel/typedefs/__init__.py +9 -0
  51. datamodel/typedefs/singleton.c +9169 -0
  52. datamodel/typedefs/singleton.cp311-win32.pyd +0 -0
  53. datamodel/typedefs/singleton.html +629 -0
  54. datamodel/typedefs/singleton.pxd +9 -0
  55. datamodel/typedefs/singleton.pyx +24 -0
  56. datamodel/typedefs/types.c +11716 -0
  57. datamodel/typedefs/types.cp311-win32.pyd +0 -0
  58. datamodel/typedefs/types.html +732 -0
  59. datamodel/typedefs/types.pxd +11 -0
  60. datamodel/typedefs/types.pyx +39 -0
  61. datamodel/types.c +7165 -0
  62. datamodel/types.cp311-win32.pyd +0 -0
  63. datamodel/types.html +716 -0
  64. datamodel/types.pyx +100 -0
  65. datamodel/validation.cp311-win32.pyd +0 -0
  66. datamodel/validation.cpp +17085 -0
  67. datamodel/validation.html +4769 -0
  68. datamodel/validation.pyx +315 -0
  69. datamodel/version.py +13 -0
  70. examples/nn/examples.py +311 -0
  71. examples/nn/stores.py +151 -0
  72. examples/tests/sp_types.py +294 -0
  73. examples/tests/speed_dates.py +26 -0
  74. python_datamodel-0.10.1.dist-info/LICENSE +29 -0
  75. python_datamodel-0.10.1.dist-info/METADATA +320 -0
  76. python_datamodel-0.10.1.dist-info/RECORD +78 -0
  77. python_datamodel-0.10.1.dist-info/WHEEL +5 -0
  78. python_datamodel-0.10.1.dist-info/top_level.txt +7 -0
datamodel/__init__.py ADDED
@@ -0,0 +1,13 @@
1
+ # -*- coding: utf-8 -*-
2
+ """DataModels.
3
+
4
+ DataModel is a reimplementation of dataclasses with true inheritance and composition.
5
+ """
6
+ from datamodel.fields import Field, Column, fields
7
+ from .models import Model
8
+ from .base import BaseModel
9
+ from .version import (
10
+ __title__, __description__, __version__, __author__, __author_email__
11
+ )
12
+
13
+ __all__ = ('fields', 'Field', 'Column', 'Model', 'BaseModel', )
datamodel/abstract.py ADDED
@@ -0,0 +1,383 @@
1
+ import contextlib
2
+ import logging
3
+ from typing import Optional, Any, List, Dict, get_args, get_origin, ClassVar
4
+ from types import GenericAlias
5
+ from collections import OrderedDict
6
+ from collections.abc import Callable
7
+ import types
8
+ from inspect import isclass
9
+ from dataclasses import dataclass, InitVar
10
+ from .parsers.json import JSONContent
11
+ from .converters import encoders, parse_basic
12
+ from .validation import validators
13
+ from .fields import Field
14
+ from .functions import (
15
+ is_dataclass,
16
+ is_primitive
17
+ )
18
+
19
+ class Meta:
20
+ """
21
+ Metadata information about Model.
22
+
23
+ Attributes:
24
+ name: str = "" name of the model
25
+ description: str = "" description of the model
26
+ schema: str = "" schema of the model (optional)
27
+ frozen: bool = False if the model (dataclass) is read-only (frozen state)
28
+ strict: bool = True if the model (dataclass) should raise an error on invalid data.
29
+ remove_null: bool = True if the model should remove null values from the data.
30
+ validate_assignment: bool = True if the model should validate during assignment.
31
+ """
32
+ name: str = ""
33
+ description: str = ""
34
+ schema: str = ""
35
+ frozen: bool = False
36
+ strict: bool = True
37
+ driver: str = None
38
+ credentials: dict = Optional[dict]
39
+ dsn: Optional[str] = None
40
+ datasource: Optional[str] = None
41
+ connection: Optional[Callable] = None
42
+ remove_nulls: bool = False
43
+ endpoint: str
44
+ extra: str = 'forbid' # could be 'allow', 'ignore', or 'forbid'
45
+ validate_assignment: bool = False
46
+ as_objects: bool = False
47
+ no_nesting: bool = False
48
+ alias_function: Optional[Callable] = None
49
+
50
+
51
+ def set_connection(cls, conn: Callable):
52
+ cls.connection = conn
53
+
54
+
55
+ def _dc_method_setattr_(self, name: str, value: Any) -> None:
56
+ """
57
+ Simplified __setattr__ for dataclass-like objects.
58
+
59
+ This version separates the known-field assignment (with optional validation)
60
+ from the “extra field” assignment and uses a helper to perform conversion/validation.
61
+ """
62
+ # Ensure that the __values__ dict is present.
63
+ if not hasattr(self, '__values__'):
64
+ object.__setattr__(self, '__values__', {})
65
+
66
+ # Check whether we are assigning to a known field.
67
+ if name in self.__fields__:
68
+ # Save the initial value (only once).
69
+ self.__values__.setdefault(name, value)
70
+
71
+ # If assignment validation is active, convert the value.
72
+ if self.Meta.validate_assignment:
73
+ value = _validate_field_assignment(self, name, value)
74
+ object.__setattr__(self, name, value)
75
+ return
76
+
77
+ # If the class is frozen, do not allow new attributes.
78
+ if self.Meta.frozen:
79
+ raise TypeError(
80
+ f"Cannot add new attribute {name!r} on {self.modelName} "
81
+ "(the class is frozen)"
82
+ )
83
+
84
+ # For extra attributes, store them as usual.
85
+ # (Note: here we “neutralize” any callable value to None if needed.)
86
+ object.__setattr__(self, name, None if callable(value) else value)
87
+ if name == '__values__':
88
+ return
89
+
90
+ # If the field isn’t known yet:
91
+ if name not in self.__fields__:
92
+ # In strict mode, we don’t allow unknown fields.
93
+ if self.Meta.strict:
94
+ return False
95
+
96
+ # Otherwise, check the "extra" policy.
97
+ extra_policy = self.Meta.extra
98
+ if extra_policy == 'forbid':
99
+ raise TypeError(f"Field {name!r} is not allowed on {self.modelName}")
100
+ elif extra_policy == 'ignore':
101
+ return
102
+
103
+ # Dynamically create a new Field for the unknown attribute.
104
+ try:
105
+ new_field = Field(required=False, default=value)
106
+ new_field.name = name
107
+ new_field.type = type(value)
108
+ # (Optionally, you might attach a parser here if validation is on.)
109
+ self.__columns__[name] = new_field
110
+ self.__fields__.append(name)
111
+ object.__setattr__(self, name, value)
112
+ except Exception as err:
113
+ logging.exception(err, stack_info=True)
114
+ raise
115
+
116
+
117
+ def _validate_field_assignment(self, name: str, value: Any) -> Any:
118
+ """
119
+ Helper that applies field conversion/validation based on cached field info.
120
+
121
+ If you cache the parser (or the type-category) on the Field during model creation,
122
+ this helper could simply call that parser.
123
+ """
124
+ field_obj = self.__columns__[name]
125
+ # _type = field_obj.type
126
+ # _encoder = field_obj.metadata.get('encoder')
127
+ # Retrieve the field category (pre‐computed at class creation)
128
+ # field_category = self.__field_types__.get(name, 'complex')
129
+ try:
130
+ return field_obj.parser(value) if field_obj.parser else value
131
+ except Exception as e:
132
+ raise TypeError(
133
+ f"Cannot assign {value!r} to field {name!r}: {e}"
134
+ ) from e
135
+
136
+
137
+ class ModelMeta(type):
138
+ """ModelMeta.
139
+
140
+ Metaclass for DataModels, convert any Model into a dataclass-compatible object.
141
+ """
142
+ __columns__: Dict
143
+ __fields__: List
144
+ __field_types__: List
145
+ __aliases__: Dict
146
+
147
+ def __new__(cls, name, bases, attrs, **kwargs): # noqa
148
+ cols = OrderedDict()
149
+ strict = False
150
+ cls.__field_types__ = {}
151
+ cls.__typing_args__ = {}
152
+ cls.__aliases__ = {}
153
+ _types = {}
154
+ _typing_args = {}
155
+ aliases = {}
156
+
157
+ if "__annotations__" in attrs:
158
+ annotations = attrs.get('__annotations__', {})
159
+ with contextlib.suppress(TypeError, AttributeError, KeyError):
160
+ strict = attrs['Meta'].strict
161
+
162
+ @staticmethod
163
+ def _initialize_fields(attrs, annotations, strict):
164
+ cols = OrderedDict()
165
+ _types_local = {}
166
+ _typing_args = {}
167
+ aliases = {}
168
+ for field, _type in annotations.items():
169
+ if isinstance(_type, InitVar) or _type == InitVar:
170
+ # Skip InitVar fields;
171
+ # they should not be part of the dataclass instance
172
+ continue
173
+ origin = get_origin(_type)
174
+ if origin is ClassVar:
175
+ continue
176
+
177
+ # Check if the field's default value is a descriptor
178
+ default_value = attrs.get(field, None)
179
+ is_descriptor = any(
180
+ hasattr(default_value, method)
181
+ for method in ("__get__", "__set__", "__delete__")
182
+ )
183
+ # Handle the descriptor field
184
+ if is_descriptor:
185
+ default_value._type_category = 'descriptor'
186
+ cols[field] = default_value
187
+ _types_local[field] = 'descriptor'
188
+ continue
189
+
190
+ if isinstance(_type, Field):
191
+ _type = _type.type
192
+ df = attrs.get(
193
+ field,
194
+ Field(type=_type, required=False, default=None)
195
+ )
196
+ if df is not None and isinstance(df, Field):
197
+ alias = df.metadata.get("alias", None)
198
+ if alias:
199
+ aliases[alias] = field
200
+ if not isinstance(df, Field):
201
+ df = Field(required=False, type=_type, default=df)
202
+ df.name = field
203
+ df.type = _type
204
+
205
+ # Cache reflection info so we DON’T need to call
206
+ # get_origin/get_args repeatedly:
207
+ args = get_args(_type)
208
+ _default = df.default
209
+ _is_dc = is_dataclass(_type)
210
+ _is_prim = is_primitive(_type)
211
+ _is_alias = isinstance(_type, GenericAlias)
212
+ _is_typing = hasattr(_type, '__module__') and _type.__module__ == 'typing' # noqa
213
+
214
+ # Store the type info in the field object:
215
+ df.is_dc = _is_dc
216
+ df.is_primitive = _is_prim
217
+ df.is_typing = _is_typing
218
+ df.origin = origin
219
+ df.args = args
220
+ df.type_args = getattr(_type, '__args__', None)
221
+
222
+ df._typeinfo_ = {
223
+ "default_callable": callable(_default)
224
+ }
225
+ # Current Field have an Encoder Function.
226
+ custom_encoder = df.metadata.get("encoder")
227
+ try:
228
+ df.parser = encoders[_type]
229
+ except (TypeError, KeyError):
230
+ df.parser = None
231
+ if custom_encoder:
232
+ df.parser = lambda value, _type=_type, encoder=custom_encoder: parse_basic(_type, value, encoder) # noqa
233
+ # Caching Validator:
234
+ try:
235
+ df.validator = validators[_type]
236
+ except (KeyError, TypeError):
237
+ df.validator = None
238
+
239
+ # check type of field:
240
+ if _is_prim:
241
+ _type_category = 'primitive'
242
+ elif origin == type:
243
+ _type_category = 'type'
244
+ elif _is_dc:
245
+ _type_category = 'dataclass'
246
+ elif _is_typing or _is_alias: # noqa
247
+ if df.origin is not None and (df.origin is list and df.args):
248
+ df._inner_type = args[0]
249
+ df._inner_origin = get_origin(df._inner_type)
250
+ df._typing_args = get_args(df._inner_type)
251
+ df._inner_is_dc = is_dataclass(df._inner_type)
252
+ try:
253
+ df._encoder_fn = encoders[df._inner_type]
254
+ except (TypeError, KeyError):
255
+ df._encoder_fn = None
256
+ if origin is list:
257
+ inner_type = args[0]
258
+ try:
259
+ df._encoder_fn = encoders[inner_type]
260
+ except (TypeError, KeyError):
261
+ df._encoder_fn = None
262
+ _type_category = 'typing'
263
+ elif isclass(_type):
264
+ _type_category = 'class'
265
+ # elif _is_alias:
266
+ # _type_category = 'typing'
267
+ else:
268
+ # TODO: making parser for complex types
269
+ _type_category = 'complex'
270
+ _types_local[field] = _type_category
271
+ df._type_category = _type_category
272
+
273
+ # Store them in a dict keyed by field name:
274
+ _typing_args[field] = (origin, args)
275
+ # Assign the field object to the attrs so dataclass can pick it up
276
+ attrs[field] = df
277
+ cols[field] = df
278
+ return cols, _types_local, _typing_args, aliases
279
+
280
+ # Initialize the fields
281
+ cols, _types, _typing_args, aliases = _initialize_fields(
282
+ attrs, annotations, strict
283
+ )
284
+ else:
285
+ # if no __annotations__, cols is empty:
286
+ cols = OrderedDict()
287
+
288
+ _columns = cols.keys()
289
+ cls.__slots__ = tuple(_columns)
290
+
291
+ # Pop Meta before creating the class so we can assign it after
292
+ attr_meta = attrs.pop("Meta", None)
293
+ # Create the class
294
+ new_cls = super().__new__(cls, name, bases, attrs, **kwargs)
295
+
296
+ # Attach Meta class
297
+ new_cls.Meta = attr_meta or getattr(new_cls, "Meta", Meta)
298
+ new_cls.__dataclass_fields__ = cols
299
+ new_cls.__typing_args__ = _typing_args
300
+ if not new_cls.Meta:
301
+ new_cls.Meta = Meta
302
+ new_cls.Meta.set_connection = types.MethodType(
303
+ set_connection, new_cls.Meta
304
+ )
305
+ try:
306
+ frozen = new_cls.Meta.frozen
307
+ except AttributeError:
308
+ new_cls.Meta.frozen = False
309
+ frozen = False
310
+
311
+ # mix values from Meta to an existing Meta Class
312
+ new_cls.Meta.__annotations__ = Meta.__annotations__
313
+ for key, _ in Meta.__annotations__.items():
314
+ if not hasattr(new_cls.Meta, key):
315
+ try:
316
+ setattr(new_cls.Meta, key, None)
317
+ except AttributeError as e:
318
+ logging.warning(
319
+ f'Missing Meta Key: {key}, {e}'
320
+ )
321
+
322
+ # If there's a __model_init__ method, call it
323
+ try:
324
+ new_cls.__model_init__(
325
+ new_cls,
326
+ name,
327
+ attrs
328
+ )
329
+ except AttributeError:
330
+ pass
331
+
332
+ # Now that fields are in attrs, decorate the class as a dataclass
333
+ dc = dataclass(
334
+ unsafe_hash=strict,
335
+ repr=False,
336
+ init=True,
337
+ order=False,
338
+ eq=True,
339
+ frozen=frozen
340
+ )(new_cls)
341
+ # Set additional attributes:
342
+ dc.__columns__ = cols
343
+ dc.__fields__ = list(_columns)
344
+ dc.__values__ = {}
345
+ dc.__encoder__ = JSONContent
346
+ dc.__valid__ = False
347
+ dc.__errors__ = None
348
+ dc.__frozen__ = strict
349
+ dc.__initialised__ = False
350
+ dc.__field_types__ = _types
351
+ dc.__aliases__ = aliases
352
+ dc.__typing_args__ = _typing_args
353
+ dc.modelName = dc.__name__
354
+
355
+ # Override __setattr__ method
356
+ setattr(dc, "__setattr__", _dc_method_setattr_)
357
+ return dc
358
+
359
+ def __init__(cls, *args, **kwargs) -> None:
360
+ # Initialized Data Model = True
361
+ cls.__initialised__ = True
362
+ cls.__errors__ = None
363
+ super().__init__(*args, **kwargs)
364
+
365
+ def __call__(cls, *args, **kwargs):
366
+ # rename any kwargs that match an alias ONLY if there are aliases defined.
367
+ alias_func = getattr(cls.Meta, "alias_function", None)
368
+ if callable(alias_func):
369
+ new_kwargs = {}
370
+ for k, v in kwargs.items():
371
+ new_k = alias_func(k)
372
+ new_kwargs[new_k] = v
373
+ kwargs = new_kwargs
374
+ if cls.__aliases__:
375
+ new_kwargs = {}
376
+ for k, v in kwargs.items():
377
+ if k in cls.__aliases__:
378
+ real_field = cls.__aliases__[k]
379
+ new_kwargs[real_field] = v
380
+ else:
381
+ new_kwargs[k] = v
382
+ kwargs = new_kwargs
383
+ return super().__call__(*args, **kwargs)
File without changes