python-datamodel 0.10.10__tar.gz → 0.10.13__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.
Files changed (101) hide show
  1. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/MANIFEST.in +2 -0
  2. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/PKG-INFO +1 -1
  3. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/abstract.py +81 -12
  4. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/models.py +17 -8
  5. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_parsers/src/lib.rs +0 -2
  6. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/version.py +1 -1
  7. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/PKG-INFO +1 -1
  8. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/CHANGELOG.md +0 -0
  9. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/CONTRIBUTING.md +0 -0
  10. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/LICENSE +0 -0
  11. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/Makefile +0 -0
  12. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/README.md +0 -0
  13. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/SECURITY.md +0 -0
  14. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/__init__.py +0 -0
  15. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/adaptive/__init__.py +0 -0
  16. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/adaptive/models.py +0 -0
  17. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/aliases/__init__.py +0 -0
  18. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/base.py +0 -0
  19. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/converters.c +0 -0
  20. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/converters.html +0 -0
  21. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/converters.pyx +0 -0
  22. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/exceptions.c +0 -0
  23. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/exceptions.html +0 -0
  24. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/exceptions.pxd +0 -0
  25. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/exceptions.pyx +0 -0
  26. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/fields.cpp +0 -0
  27. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/fields.html +0 -0
  28. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/fields.pyx +0 -0
  29. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/functions.cpp +0 -0
  30. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/functions.html +0 -0
  31. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/functions.pxd +0 -0
  32. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/functions.pyx +0 -0
  33. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/jsonld/__init__.py +0 -0
  34. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/jsonld/models.py +0 -0
  35. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/__init__.py +0 -0
  36. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/mapping.c +0 -0
  37. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/mapping.html +0 -0
  38. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/mapping.pxd +0 -0
  39. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/mapping.pyx +0 -0
  40. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/libs/mutables.py +0 -0
  41. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/parsers/__init__.py +0 -0
  42. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/parsers/encoders.py +0 -0
  43. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/parsers/json.cpp +0 -0
  44. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/parsers/json.html +0 -0
  45. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/parsers/json.pyx +0 -0
  46. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/profiler.py +0 -0
  47. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/py.typed +0 -0
  48. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_core/Cargo.toml +0 -0
  49. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_core/src/lib.rs +0 -0
  50. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_parsers/Cargo.toml +0 -0
  51. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_validators/Cargo.toml +0 -0
  52. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/rs_validators/src/lib.rs +0 -0
  53. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/__init__.py +0 -0
  54. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/singleton.c +0 -0
  55. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/singleton.html +0 -0
  56. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/singleton.pxd +0 -0
  57. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/singleton.pyx +0 -0
  58. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/types.c +0 -0
  59. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/types.html +0 -0
  60. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/types.pxd +0 -0
  61. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/typedefs/types.pyx +0 -0
  62. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/types.c +0 -0
  63. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/types.html +0 -0
  64. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/types.pyx +0 -0
  65. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/validation.cpp +0 -0
  66. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/validation.html +0 -0
  67. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/datamodel/validation.pyx +0 -0
  68. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/pyproject.toml +0 -0
  69. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/SOURCES.txt +0 -0
  70. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/dependency_links.txt +0 -0
  71. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/not-zip-safe +0 -0
  72. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/requires.txt +0 -0
  73. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/python_datamodel.egg-info/top_level.txt +0 -0
  74. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/setup.cfg +0 -0
  75. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/setup.py +0 -0
  76. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/__init__.py +0 -0
  77. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_aliases.py +0 -0
  78. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_classdict.py +0 -0
  79. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_converter.py +0 -0
  80. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_dashboards.py +0 -0
  81. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_data.py +0 -0
  82. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_descriptors.py +0 -0
  83. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_dict.py +0 -0
  84. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_field.py +0 -0
  85. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_inherit.py +0 -0
  86. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_json.py +0 -0
  87. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_method.py +0 -0
  88. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_model.py +0 -0
  89. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_notify.py +0 -0
  90. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_primitives.py +0 -0
  91. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_qsdriver.py +0 -0
  92. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_qsmodel.py +0 -0
  93. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_qsobject.py +0 -0
  94. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_ticket.py +0 -0
  95. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_tickets.py +0 -0
  96. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_tuples.py +0 -0
  97. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_type_user.py +0 -0
  98. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_types.py +0 -0
  99. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_unions.py +0 -0
  100. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_valid_callables.py +0 -0
  101. {python_datamodel-0.10.10 → python_datamodel-0.10.13}/tests/test_validations.py +0 -0
@@ -13,6 +13,8 @@ recursive-include datamodel/rs_parsers *
13
13
 
14
14
  # Exclude tests, settings, env, examples, and bin folders
15
15
  global-exclude *.pyc
16
+ recursive-exclude datamodel/rs_parsers/target *
17
+
16
18
  prune docs
17
19
  prune settings
18
20
  prune env
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-datamodel
3
- Version: 0.10.10
3
+ Version: 0.10.13
4
4
  Summary: simple library based on python +3.8 to use Dataclass-syntaxfor interacting with Data
5
5
  Home-page: https://github.com/phenobarbital/python-datamodel
6
6
  Author: Jesus Lara
@@ -16,6 +16,7 @@ from collections import OrderedDict
16
16
  from collections.abc import Callable
17
17
  import types
18
18
  from inspect import isclass
19
+ from functools import lru_cache
19
20
  from dataclasses import dataclass, InitVar
20
21
  from .parsers.json import JSONContent
21
22
  from .converters import encoders, parse_basic
@@ -153,8 +154,27 @@ class ModelMeta(type):
153
154
  __fields__: List
154
155
  __field_types__: List
155
156
  __aliases__: Dict
157
+ __primary_keys__: List
156
158
  # Class-level cache
157
- _base_class_cache = {}
159
+ _base_class_cache = OrderedDict()
160
+ _MAX_CACHE_SIZE = 512 # size limit
161
+
162
+ @classmethod
163
+ def _cache_get(cls, key):
164
+ """Get item from cache with LRU behavior."""
165
+ if key not in cls._base_class_cache:
166
+ return None
167
+ value = cls._base_class_cache.pop(key)
168
+ cls._base_class_cache[key] = value
169
+ return value
170
+
171
+ @classmethod
172
+ def _cache_set(cls, key, value):
173
+ """Set item in cache with LRU eviction."""
174
+ # If cache is full, remove oldest item (first in OrderedDict)
175
+ if len(cls._base_class_cache) >= cls._MAX_CACHE_SIZE:
176
+ cls._base_class_cache.popitem(last=False)
177
+ cls._base_class_cache[key] = value
158
178
 
159
179
  @staticmethod
160
180
  def _initialize_fields(attrs, annotations, strict):
@@ -162,6 +182,8 @@ class ModelMeta(type):
162
182
  _types_local = {}
163
183
  _typing_args = {}
164
184
  aliases = {}
185
+ primary_keys = [] # New list to collect primary key fields
186
+
165
187
  for field, _type in annotations.items():
166
188
  if isinstance(_type, InitVar) or _type == InitVar:
167
189
  # Skip InitVar fields;
@@ -202,6 +224,10 @@ class ModelMeta(type):
202
224
  df.name = field
203
225
  df.type = _type
204
226
 
227
+ # Check for primary_key in field metadata
228
+ if df.metadata.get("primary", False) or df.primary_key is True:
229
+ primary_keys.append(field)
230
+
205
231
  # Cache reflection info so we DON’T need to call
206
232
  # get_origin/get_args repeatedly:
207
233
  args = get_args(_type)
@@ -281,26 +307,35 @@ class ModelMeta(type):
281
307
  # Assign the field object to the attrs so dataclass can pick it up
282
308
  attrs[field] = df
283
309
  cols[field] = df
284
- return cols, _types_local, _typing_args, aliases
310
+ return cols, _types_local, _typing_args, aliases, primary_keys
285
311
 
286
312
  def __new__(cls, name, bases, attrs, **kwargs): # noqa
287
313
  annotations = attrs.get('__annotations__', {})
288
- base_key = (tuple(bases), tuple(sorted(annotations.items())))
289
- strict = getattr(attrs.get('Meta', Meta), 'strict', False)
314
+ _strict_ = False
315
+ cols = OrderedDict()
290
316
 
291
- if base_key in cls._base_class_cache:
317
+ # Base class constructor
318
+ base_key = (name, tuple(bases), tuple(sorted(annotations.items())))
319
+ with contextlib.suppress(TypeError, AttributeError, KeyError):
320
+ _strict_ = attrs['Meta'].strict
321
+
322
+ # Use LRU get method
323
+ cached = cls._cache_get(base_key)
324
+ if cached:
325
+ # if base_key in cls._base_class_cache:
292
326
  # Check the Cache First:
293
- cached = cls._base_class_cache[base_key]
327
+ # cached = cls._base_class_cache[base_key]
294
328
  cols = cached['cols'].copy()
295
329
  _types = cached['types'].copy()
296
330
  _typing_args = cached['_typing_args'].copy()
297
331
  aliases = cached['aliases'].copy()
332
+ primary_keys = cached['primary_keys'].copy()
298
333
  else:
299
334
  # Compute field from Bases:
300
- cols = OrderedDict()
301
335
  _types = {}
302
336
  _typing_args = {}
303
337
  aliases = {}
338
+ primary_keys = []
304
339
 
305
340
  # Step 1: Collect fields from parent classes
306
341
  for base in bases:
@@ -311,10 +346,12 @@ class ModelMeta(type):
311
346
  _typing_args |= base.__typing_args__
312
347
  if hasattr(base, '__aliases__'):
313
348
  aliases |= base.__aliases__
349
+ if hasattr(base, '__primary_keys__'):
350
+ primary_keys += base.__primary_keys__
314
351
 
315
352
  # Now initialize subclass-specific fields
316
- new_cols, new_types, new_typing_args, new_aliases = cls._initialize_fields(
317
- attrs, annotations, strict
353
+ new_cols, new_types, new_typing_args, new_aliases, nw_primary_keys = cls._initialize_fields( # noqa
354
+ attrs, annotations, _strict_
318
355
  )
319
356
 
320
357
  # Merge new fields with inherited fields
@@ -322,14 +359,24 @@ class ModelMeta(type):
322
359
  _types.update(new_types)
323
360
  _typing_args.update(new_typing_args)
324
361
  aliases.update(new_aliases)
362
+ primary_keys.extend(nw_primary_keys)
325
363
 
326
364
  # Store computed results in cache
327
- cls._base_class_cache[base_key] = {
365
+ # cls._base_class_cache[base_key] = {
366
+ # 'cols': cols.copy(),
367
+ # 'types': _types.copy(),
368
+ # '_typing_args': _typing_args.copy(),
369
+ # 'aliases': aliases.copy(),
370
+ # 'primary_keys': primary_keys.copy(),
371
+ # }
372
+ cache_entry = {
328
373
  'cols': cols.copy(),
329
374
  'types': _types.copy(),
330
375
  '_typing_args': _typing_args.copy(),
331
376
  'aliases': aliases.copy(),
377
+ 'primary_keys': primary_keys.copy(),
332
378
  }
379
+ cls._cache_set(base_key, cache_entry)
333
380
 
334
381
  _columns = cols.keys()
335
382
  cls.__slots__ = tuple(_columns)
@@ -375,7 +422,7 @@ class ModelMeta(type):
375
422
 
376
423
  # Now that fields are in attrs, decorate the class as a dataclass
377
424
  dc = dataclass(
378
- unsafe_hash=strict,
425
+ unsafe_hash=_strict_,
379
426
  repr=False,
380
427
  init=True,
381
428
  order=False,
@@ -389,15 +436,37 @@ class ModelMeta(type):
389
436
  dc.__valid__ = False
390
437
  dc.__errors__ = {}
391
438
  dc.__values__ = {}
392
- dc.__frozen__ = strict
439
+ dc.__frozen__ = _strict_
393
440
  dc.__initialised__ = False
394
441
  dc.__field_types__ = _types
395
442
  dc.__aliases__ = aliases
443
+ # Set the primary_keys on the dataclass
444
+ dc.__primary_keys__ = primary_keys
396
445
  dc.__typing_args__ = _typing_args
397
446
  dc.modelName = dc.__name__
398
447
 
399
448
  # Override __setattr__ method
400
449
  setattr(dc, "__setattr__", _dc_method_setattr_)
450
+
451
+ # Method to get primary keys without further introspection
452
+ def get_primary_keys(cls):
453
+ return cls.__primary_keys__
454
+
455
+ # Add the method to the class
456
+ dc.get_primary_keys = classmethod(get_primary_keys)
457
+
458
+ def get_primary_key_fields(cls):
459
+ """Return a dictionary of primary key fields with their types."""
460
+ return {name: cls.__columns__[name] for name in cls.__primary_keys__}
461
+
462
+ dc.get_primary_key_fields = classmethod(get_primary_key_fields)
463
+
464
+ def get_primary_key_values(self):
465
+ """Get primary key values for this instance as a dictionary."""
466
+ return {key: getattr(self, key) for key in self.__primary_keys__}
467
+
468
+ dc.get_primary_key_values = get_primary_key_values
469
+
401
470
  return dc
402
471
 
403
472
  def __init__(cls, *args, **kwargs) -> None:
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
  import contextlib
3
- from typing import Any, Dict
3
+ from typing import Any, Dict, get_args
4
4
  from enum import Enum, EnumMeta
5
5
  # Dataclass
6
6
  import inspect
@@ -250,13 +250,22 @@ class ModelMixin:
250
250
  )
251
251
  # if it's a list, might contain submodels or scalars
252
252
  elif isinstance(value, list):
253
- if field.origin is list and field.args:
254
- submodel_class = field.args[0] # The type inside the list
255
- if issubclass(
256
- submodel_class, ModelMixin
257
- ) and not hasattr(submodel_class, name):
258
- out[name] = json_encoder(value)
259
- continue
253
+ targs = field.args
254
+ if len(targs) == 2 and targs[1] is type(None):
255
+ # Checking if it's Optional[T]
256
+ submodel_class = get_args(targs[0])
257
+ elif field.origin is list and field.args:
258
+ submodel_class = field.args[0]
259
+ else:
260
+ submodel_class = None
261
+ if field.origin is list and submodel_class and (
262
+ issubclass(
263
+ submodel_class,
264
+ ModelMixin
265
+ ) and not hasattr(submodel_class, name)
266
+ ):
267
+ out[name] = json_encoder(value)
268
+ continue
260
269
  items_out = []
261
270
  for item in value:
262
271
  if isinstance(item, ModelMixin):
@@ -5,12 +5,10 @@ use pyo3::wrap_pyfunction;
5
5
  use pyo3::types::{PyDate, PyDateTime, PyAny, PyString, PyBool, PyBytes, PyInt, PyFloat, PyList};
6
6
  use chrono::{Datelike, Timelike, NaiveDate, NaiveDateTime, DateTime, Utc};
7
7
  use speedate::Date as SpeeDate;
8
- use std::sync::Mutex;
9
8
  use speedate::DateTime as SpeeDateTime;
10
9
  use uuid::Uuid;
11
10
  use rust_decimal::Decimal; // Rust Decimal crate
12
11
  use rust_decimal::prelude::FromStr;
13
- use rayon::prelude::*;
14
12
  // use speedate::{Date, DateTime, ParseError};
15
13
  // use std::collections::HashMap;
16
14
  // NaiveTime
@@ -6,7 +6,7 @@ __description__ = (
6
6
  'simple library based on python +3.8 to use Dataclass-syntax'
7
7
  'for interacting with Data'
8
8
  )
9
- __version__ = '0.10.10'
9
+ __version__ = '0.10.13'
10
10
  __copyright__ = 'Copyright (c) 2020-2024 Jesus Lara'
11
11
  __author__ = 'Jesus Lara'
12
12
  __author_email__ = 'jesuslarag@gmail.com'
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-datamodel
3
- Version: 0.10.10
3
+ Version: 0.10.13
4
4
  Summary: simple library based on python +3.8 to use Dataclass-syntaxfor interacting with Data
5
5
  Home-page: https://github.com/phenobarbital/python-datamodel
6
6
  Author: Jesus Lara