python-datamodel 0.6.17__tar.gz → 0.10.1__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 (119) hide show
  1. python_datamodel-0.10.1/CHANGELOG.md +17 -0
  2. python_datamodel-0.10.1/CONTRIBUTING.md +60 -0
  3. python_datamodel-0.10.1/MANIFEST.in +22 -0
  4. python_datamodel-0.10.1/Makefile +37 -0
  5. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/PKG-INFO +17 -12
  6. python_datamodel-0.10.1/SECURITY.md +18 -0
  7. python_datamodel-0.10.1/datamodel/abstract.py +383 -0
  8. python_datamodel-0.10.1/datamodel/adaptive/models.py +598 -0
  9. python_datamodel-0.10.1/datamodel/aliases/__init__.py +26 -0
  10. python_datamodel-0.10.1/datamodel/base.py +180 -0
  11. python_datamodel-0.10.1/datamodel/converters.c +43471 -0
  12. python_datamodel-0.10.1/datamodel/converters.html +17387 -0
  13. python_datamodel-0.10.1/datamodel/converters.pyx +1489 -0
  14. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/exceptions.c +1016 -651
  15. python_datamodel-0.10.1/datamodel/exceptions.html +1261 -0
  16. python_datamodel-0.10.1/datamodel/exceptions.pxd +13 -0
  17. python_datamodel-0.10.1/datamodel/exceptions.pyx +50 -0
  18. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/fields.cpp +1853 -1146
  19. python_datamodel-0.10.1/datamodel/fields.html +3912 -0
  20. python_datamodel-0.10.1/datamodel/fields.pyx +309 -0
  21. python-datamodel-0.6.17/datamodel/validation.cpp → python_datamodel-0.10.1/datamodel/functions.cpp +2771 -5918
  22. python_datamodel-0.10.1/datamodel/functions.html +1766 -0
  23. python_datamodel-0.10.1/datamodel/functions.pxd +9 -0
  24. python_datamodel-0.10.1/datamodel/functions.pyx +82 -0
  25. python_datamodel-0.10.1/datamodel/jsonld/__init__.py +45 -0
  26. python_datamodel-0.10.1/datamodel/jsonld/models.py +500 -0
  27. python_datamodel-0.10.1/datamodel/libs/__init__.py +1 -0
  28. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/libs/mapping.c +1441 -1179
  29. python_datamodel-0.10.1/datamodel/libs/mapping.html +2618 -0
  30. python_datamodel-0.10.1/datamodel/libs/mapping.pxd +11 -0
  31. python_datamodel-0.10.1/datamodel/libs/mapping.pyx +135 -0
  32. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/libs/mutables.py +19 -8
  33. python_datamodel-0.10.1/datamodel/models.py +814 -0
  34. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/parsers/encoders.py +1 -1
  35. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/parsers/json.cpp +7176 -3159
  36. python_datamodel-0.10.1/datamodel/parsers/json.html +3365 -0
  37. python_datamodel-0.10.1/datamodel/parsers/json.pyx +250 -0
  38. python_datamodel-0.10.1/datamodel/rs_core/Cargo.toml +17 -0
  39. python_datamodel-0.10.1/datamodel/rs_core/src/lib.rs +294 -0
  40. python_datamodel-0.10.1/datamodel/rs_parsers/Cargo.toml +22 -0
  41. python_datamodel-0.10.1/datamodel/rs_parsers/src/lib.rs +571 -0
  42. python_datamodel-0.10.1/datamodel/rs_validators/Cargo.toml +17 -0
  43. python_datamodel-0.10.1/datamodel/typedefs/__init__.py +9 -0
  44. python_datamodel-0.10.1/datamodel/typedefs/singleton.c +9169 -0
  45. python_datamodel-0.10.1/datamodel/typedefs/singleton.html +629 -0
  46. python_datamodel-0.10.1/datamodel/typedefs/singleton.pxd +9 -0
  47. python_datamodel-0.10.1/datamodel/typedefs/singleton.pyx +24 -0
  48. python_datamodel-0.10.1/datamodel/typedefs/types.c +11716 -0
  49. python_datamodel-0.10.1/datamodel/typedefs/types.html +732 -0
  50. python_datamodel-0.10.1/datamodel/typedefs/types.pxd +11 -0
  51. python_datamodel-0.10.1/datamodel/typedefs/types.pyx +39 -0
  52. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/types.c +482 -465
  53. python_datamodel-0.10.1/datamodel/types.html +716 -0
  54. python_datamodel-0.10.1/datamodel/types.pyx +100 -0
  55. python_datamodel-0.10.1/datamodel/validation.cpp +17085 -0
  56. python_datamodel-0.10.1/datamodel/validation.html +4769 -0
  57. python_datamodel-0.10.1/datamodel/validation.pyx +315 -0
  58. python_datamodel-0.10.1/datamodel/version.py +13 -0
  59. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/pyproject.toml +24 -7
  60. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/python_datamodel.egg-info/PKG-INFO +17 -12
  61. python_datamodel-0.10.1/python_datamodel.egg-info/SOURCES.txt +97 -0
  62. python_datamodel-0.10.1/python_datamodel.egg-info/not-zip-safe +1 -0
  63. python_datamodel-0.10.1/python_datamodel.egg-info/requires.txt +13 -0
  64. python_datamodel-0.10.1/python_datamodel.egg-info/top_level.txt +5 -0
  65. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/setup.cfg +1 -1
  66. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/setup.py +75 -28
  67. python_datamodel-0.10.1/tests/__init__.py +0 -0
  68. python_datamodel-0.10.1/tests/test_aliases.py +78 -0
  69. python_datamodel-0.10.1/tests/test_converter.py +170 -0
  70. python_datamodel-0.10.1/tests/test_data.py +110 -0
  71. python_datamodel-0.10.1/tests/test_descriptors.py +88 -0
  72. python_datamodel-0.10.1/tests/test_dict.py +51 -0
  73. python_datamodel-0.10.1/tests/test_inherit.py +171 -0
  74. python_datamodel-0.10.1/tests/test_json.py +237 -0
  75. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_method.py +2 -1
  76. python_datamodel-0.10.1/tests/test_primitives.py +146 -0
  77. python_datamodel-0.10.1/tests/test_qsdriver.py +184 -0
  78. python_datamodel-0.10.1/tests/test_qsmodel.py +219 -0
  79. python_datamodel-0.10.1/tests/test_ticket.py +157 -0
  80. python_datamodel-0.10.1/tests/test_tickets.py +228 -0
  81. python_datamodel-0.10.1/tests/test_tuples.py +47 -0
  82. python_datamodel-0.10.1/tests/test_type_user.py +62 -0
  83. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_types.py +29 -21
  84. python_datamodel-0.10.1/tests/test_valid_callables.py +67 -0
  85. python_datamodel-0.10.1/tests/test_validations.py +251 -0
  86. python-datamodel-0.6.17/datamodel/abstract.py +0 -187
  87. python-datamodel-0.6.17/datamodel/base.py +0 -581
  88. python-datamodel-0.6.17/datamodel/converters.c +0 -25047
  89. python-datamodel-0.6.17/datamodel/models.py +0 -111
  90. python-datamodel-0.6.17/datamodel/version.py +0 -9
  91. python-datamodel-0.6.17/examples/basic.py +0 -152
  92. python-datamodel-0.6.17/examples/check.py +0 -91
  93. python-datamodel-0.6.17/examples/example.py +0 -190
  94. python-datamodel-0.6.17/examples/inherit.py +0 -40
  95. python-datamodel-0.6.17/examples/model.py +0 -63
  96. python-datamodel-0.6.17/examples/payroll.py +0 -113
  97. python-datamodel-0.6.17/examples/person.py +0 -49
  98. python-datamodel-0.6.17/examples/polymorph.py +0 -60
  99. python-datamodel-0.6.17/examples/schema.py +0 -68
  100. python-datamodel-0.6.17/examples/test_actor.py +0 -42
  101. python-datamodel-0.6.17/examples/test_model.py +0 -73
  102. python-datamodel-0.6.17/examples/test_nulls.py +0 -59
  103. python-datamodel-0.6.17/python_datamodel.egg-info/SOURCES.txt +0 -48
  104. python-datamodel-0.6.17/python_datamodel.egg-info/requires.txt +0 -12
  105. python-datamodel-0.6.17/python_datamodel.egg-info/top_level.txt +0 -1
  106. python-datamodel-0.6.17/tests/test_inherit.py +0 -47
  107. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/LICENSE +0 -0
  108. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/README.md +0 -0
  109. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/__init__.py +0 -0
  110. {python-datamodel-0.6.17/datamodel/libs → python_datamodel-0.10.1/datamodel/adaptive}/__init__.py +0 -0
  111. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/parsers/__init__.py +0 -0
  112. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/profiler.py +0 -0
  113. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/datamodel/py.typed +0 -0
  114. /python-datamodel-0.6.17/examples/__init__.py → /python_datamodel-0.10.1/datamodel/rs_validators/src/lib.rs +0 -0
  115. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/python_datamodel.egg-info/dependency_links.txt +0 -0
  116. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_classdict.py +0 -0
  117. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_field.py +0 -0
  118. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_model.py +0 -0
  119. {python-datamodel-0.6.17 → python_datamodel-0.10.1}/tests/test_qsobject.py +0 -0
@@ -0,0 +1,17 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this
6
+ project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+ ## [0.0.15] - 2022-09-15
8
+ * fixing building wheel for x86_64
9
+ * fixing behaviors over Meta class in Models with missing attributes
10
+
11
+ ## [0.0.7] - 2022-09-14
12
+ * Added "from_dict" and "from_json" methods to create datamodels from json strings and dictionaries
13
+ * added a new json encoder, based on orjson
14
+ * "model()" method export a json version of Model (serialization).
15
+
16
+ ## [0.0.1] - 2022-09-12
17
+ * First version
@@ -0,0 +1,60 @@
1
+ # Contributing to DataModel
2
+
3
+ ## Preparation
4
+
5
+ DataModel is designed to use last syntax of asyncio-tools, for that reason, you'll need to have at least Python 3.8 available for testing.
6
+
7
+ You can do this with [pyenv][]:
8
+
9
+
10
+ $ pyenv install 3.8.1
11
+ $ pyenv local 3.8.1
12
+
13
+ Or using virtualenv:
14
+
15
+ $ python3.8 -m venv .venv
16
+
17
+ Also, we can use the command "make venv" inside of Makefile.
18
+
19
+ $ make venv
20
+
21
+ ## Setup
22
+
23
+ Once cloned, create a clean virtual environment and
24
+ install the appropriate tools and dependencies:
25
+
26
+ $ cd <path/to/DataModel>
27
+ $ make venv
28
+ $ source .venv/bin/activate
29
+ $ make setup
30
+
31
+
32
+ ## Formatting
33
+
34
+ DataModel start using *[black][]* for formating code.
35
+
36
+ $ make format
37
+
38
+
39
+ ## Testing
40
+
41
+ Once you've made changes, you should run unit tests,
42
+ validate your type annotations, and ensure your code
43
+ meets the appropriate style and linting rules:
44
+
45
+ $ make test lint
46
+
47
+
48
+ ## Submitting
49
+
50
+ Before submitting a pull request, please ensure
51
+ that you have done the following:
52
+
53
+ * Documented changes or features in README.md
54
+ * Added appropriate license headers to new files
55
+ * Written or modified tests for new functionality
56
+ * Formatted code following project standards
57
+ * Validated code and formatting with `make test lint`
58
+
59
+ [black]: https://github.com/psf/black
60
+ [pyenv]: https://github.com/pyenv/pyenv
@@ -0,0 +1,22 @@
1
+ include LICENSE
2
+ include CHANGELOG.md
3
+ include CONTRIBUTING.md
4
+ include SECURITY.md
5
+ include README.md
6
+ include Makefile
7
+
8
+ graft datamodel
9
+ graft tests
10
+
11
+ recursive-include datamodel *.pxd *.pyx
12
+ recursive-include datamodel/rs_parsers *
13
+
14
+ # Exclude tests, settings, env, examples, and bin folders
15
+ global-exclude *.pyc
16
+ prune docs
17
+ prune settings
18
+ prune env
19
+ prune examples
20
+ prune bin
21
+ recursive-exclude */__pycache__
22
+ prune */__pycache__
@@ -0,0 +1,37 @@
1
+ venv:
2
+ python3.11 -m venv .venv
3
+ echo 'run `source .venv/bin/activate` to start develop DataModel'
4
+
5
+ venv12:
6
+ python3.12 -m venv .venv11
7
+ echo 'run `source .venv/bin/activate` to start develop DataModel'
8
+
9
+ install:
10
+ pip install -e .
11
+
12
+ develop:
13
+ pip install -e .
14
+ pip install -Ur docs/requirements-dev.txt
15
+
16
+ compile:
17
+ python setup.py build_ext --inplace
18
+
19
+ release:
20
+ lint test clean
21
+ flit publish
22
+
23
+ format:
24
+ python -m black datamodel
25
+
26
+ lint:
27
+ python -m pylint --rcfile .pylintrc datamodels/*.py
28
+ python -m pylint --rcfile .pylintrc datamodels/models/*.py
29
+ python -m black --check datamodels
30
+
31
+ test:
32
+ python -m coverage run -m datamodels.tests
33
+ python -m coverage report
34
+ python -m mypy datamodels/*.py
35
+
36
+ distclean:
37
+ rm -rf .venv
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: python-datamodel
3
- Version: 0.6.17
3
+ Version: 0.10.1
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
7
7
  Author-email: jesuslarag@gmail.com
8
8
  License: BSD
9
- Project-URL: Source, https://github.com/phenobarbital/datamodels
9
+ Project-URL: Source, https://github.com/phenobarbital/datamodel
10
10
  Project-URL: Funding, https://paypal.me/phenobarbital
11
+ Project-URL: Tracker, https://github.com/phenobarbital/datamodel/issues
12
+ Project-URL: Documentation, https://datamodel.readthedocs.io/en/latest/
13
+ Project-URL: Buy Me A Coffee!, https://www.buymeacoffee.com/phenobarbital
11
14
  Project-URL: Say Thanks!, https://saythanks.io/to/phenobarbital
12
15
  Keywords: asyncio,dataclass,dataclasses,data models
13
16
  Platform: any
@@ -18,30 +21,32 @@ Classifier: Topic :: Software Development :: Build Tools
18
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
22
  Classifier: Programming Language :: Python :: 3
20
23
  Classifier: Programming Language :: Python :: 3 :: Only
21
- Classifier: Programming Language :: Python :: 3.9
22
24
  Classifier: Programming Language :: Python :: 3.10
23
25
  Classifier: Programming Language :: Python :: 3.11
26
+ Classifier: Programming Language :: Python :: 3.12
27
+ Classifier: Programming Language :: Python :: 3.13
24
28
  Classifier: Framework :: AsyncIO
25
29
  Classifier: License :: OSI Approved :: BSD License
26
30
  Classifier: Operating System :: OS Independent
27
31
  Classifier: Topic :: System :: Systems Administration
28
32
  Classifier: Topic :: Utilities
29
33
  Classifier: Environment :: Web Environment
30
- Requires-Python: >=3.9.14
34
+ Requires-Python: >=3.10.0
31
35
  Description-Content-Type: text/markdown
32
36
  License-File: LICENSE
33
- Requires-Dist: numpy==1.24.2
34
- Requires-Dist: uvloop==0.19.0
37
+ Requires-Dist: numpy>=1.26.4
38
+ Requires-Dist: uvloop>=0.21.0
35
39
  Requires-Dist: asyncio==3.4.3
36
40
  Requires-Dist: faust-cchardet==2.1.19
37
- Requires-Dist: ciso8601==2.3.1
41
+ Requires-Dist: ciso8601==2.3.2
38
42
  Requires-Dist: objectpath==0.6.1
39
- Requires-Dist: orjson==3.9.10
40
- Requires-Dist: typing_extensions==4.8.0
41
- Requires-Dist: asyncpg==0.29.0
42
- Requires-Dist: python-dateutil==2.8.2
43
- Requires-Dist: pendulum==2.1.2
43
+ Requires-Dist: orjson>=3.10.11
44
+ Requires-Dist: typing_extensions>=4.9.0
45
+ Requires-Dist: asyncpg>=0.29.0
46
+ Requires-Dist: python-dateutil>=2.8.2
44
47
  Requires-Dist: python-slugify==8.0.1
48
+ Requires-Dist: psycopg2-binary==2.9.10
49
+ Requires-Dist: msgspec==0.19.0
45
50
 
46
51
  # DataModel
47
52
  DataModel is a simple library based on python +3.8 to use Dataclass-syntax for interacting with
@@ -0,0 +1,18 @@
1
+ # Security Policy
2
+
3
+ ## Supported Versions
4
+
5
+ We release patches for security vulnerabilities. Which versions are eligible
6
+ receiving such patches depend on the CVSS v3.0 Rating:
7
+
8
+ | CVSS v3.0 | Supported Versions |
9
+ | --------- | ----------------------------------------- |
10
+ | 9.0-10.0 | Releases within the previous three months |
11
+ | 4.0-8.9 | Most recent release |
12
+
13
+ ## Reporting a Vulnerability
14
+
15
+ Please report (suspected) security vulnerabilities to
16
+ **[jesularag@gmail.com](mailto:jesularag@gmail.com)**. You will receive a response from
17
+ us within 48 hours. If the issue is confirmed, we will release a patch as soon
18
+ as possible depending on complexity but historically within a few days.
@@ -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)