statica 1.2.0__tar.gz → 1.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statica
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: A minimalistic data validation library
5
5
  Author-email: Marcel Kröker <kroeker.marcel@gmail.com>
6
6
  License: MIT License
@@ -31,10 +31,11 @@ Classifier: Operating System :: OS Independent
31
31
  Classifier: Intended Audience :: Developers
32
32
  Classifier: Programming Language :: Python
33
33
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
34
- Requires-Python: <3.14,>=3.10
34
+ Requires-Python: <3.14,>=3.11
35
35
  Description-Content-Type: text/markdown
36
36
 
37
- # Statica
37
+ Statica
38
+ ========================================================================================
38
39
 
39
40
  ![Tests](https://github.com/mkrd/statica/actions/workflows/test.yml/badge.svg)
40
41
  ![Coverage](https://github.com/mkrd/statica/blob/main/assets/coverage.svg?raw=true)
@@ -42,17 +43,21 @@ Description-Content-Type: text/markdown
42
43
 
43
44
  Statica is a Python library for defining and validating structured data with type annotations and constraints. It provides an easy-to-use framework for creating type-safe models with comprehensive validation for both types and constraints.
44
45
 
45
- ## Why Statica?
46
46
 
47
- Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like pydantic.
47
+ Why Statica?
48
+ ----------------------------------------------------------------------------------------
49
+
50
+ Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like Pydantic.
48
51
  While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
49
52
 
50
- 1. **Lightweight**: Statica does not rely on any third-party dependencies, making it ideal for projects where minimizing external dependencies is a priority.
53
+ 1. **Lightweight**: Statica has zero dependencies, making it ideal for projects where minimizing external dependencies is a priority.
51
54
  2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
52
- 3. **Ease of Use**: With its simple, Pythonic design, Statica is intuitive for developers already familiar with Python's `dataclasses` and type hinting. It avoids much of the magic that pydantic employs.
53
- 4. **Customizable Validation**: Statica allows fine-grained control over type and constraint validation through customizable field descriptors (`Field`) and error classes.
55
+ 3. **Ease of Use**: With its simple, Pythonic design, Statica is intuitive for developers already familiar with Python's `dataclasses` and type hinting. It avoids much of the magic and complexity of Pydantic.
56
+ 4. **Customizable**: Statica allows fine-grained control over type and constraint validation through customizable fields and error classes.
57
+
54
58
 
55
- ## Features
59
+ Features
60
+ ----------------------------------------------------------------------------------------
56
61
 
57
62
  - **Type Validation**: Automatically validates types for attributes based on type hints.
58
63
  - **Constraint Validation**: Define constraints like minimum/maximum length, value ranges, and more.
@@ -62,8 +67,11 @@ While pydantic is a powerful tool for data validation and parsing, Statica offer
62
67
  - **Automatic Initialization**: Automatically generate constructors (`__init__`) for your models.
63
68
  - **String Manipulation**: Strip whitespace from string fields if needed.
64
69
  - **Casting**: Automatically cast values to the desired type.
70
+ - **Field Aliasing**: Support for field aliases for parsing and serialization.
65
71
 
66
- ## Installation
72
+
73
+ Installation
74
+ ----------------------------------------------------------------------------------------
67
75
 
68
76
  You can install Statica via pip:
69
77
 
@@ -71,7 +79,9 @@ You can install Statica via pip:
71
79
  pip install statica
72
80
  ```
73
81
 
74
- ## Getting Started
82
+
83
+ Getting Started
84
+ ----------------------------------------------------------------------------------------
75
85
 
76
86
  ### Basic Usage
77
87
 
@@ -177,20 +187,98 @@ except CustomError as e:
177
187
  print(e) # Output: "num: must be at least 1"
178
188
  ```
179
189
 
180
- ## Advanced Usage
190
+ Or, define a BaseClass which configures the error classes globally:
191
+
192
+ ```python
193
+ from statica.core import Statica, ConstraintValidationError, TypeValidationError
194
+
195
+ class BaseClass(Statica):
196
+ constraint_error_class = ConstraintValidationError
197
+ type_error_class = TypeValidationError
198
+
199
+ class CustomPayload(BaseClass):
200
+ num: int = Field(min_value=1, max_value=10)
201
+
202
+ try:
203
+ payload = CustomPayload(num=0)
204
+ except ConstraintValidationError as e:
205
+ print(e) # Output: "num: must be at least 1"
206
+ ```
207
+
181
208
 
182
- ### Short Syntax for Fields
209
+ Aliasing
210
+ ----------------------------------------------------------------------------------------
183
211
 
184
- You can use simple type annotations without explicitly defining `Field` descriptors:
212
+ Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
213
+ This is particularly useful when working with external APIs that use different naming conventions.
214
+
215
+ ### Basic Aliases
216
+
217
+ Use the `alias` parameter to define an alternative name for both parsing and serialization:
218
+
219
+ ```python
220
+ class User(Statica):
221
+ full_name: str = Field(alias="fullName")
222
+ age: int = Field(alias="userAge")
223
+
224
+ # Parse data with aliases
225
+ data = {"fullName": "John Doe", "userAge": 30}
226
+ user = User.from_map(data)
227
+ print(user.full_name) # Output: "John Doe"
228
+ print(user.age) # Output: 30
229
+
230
+ # Serialize back with aliases
231
+ result = user.to_dict()
232
+ print(result) # Output: {"fullName": "John Doe", "userAge": 30}
233
+ ```
234
+
235
+ ### Separate Parsing and Serialization Aliases
236
+
237
+ You can define different aliases for parsing and serialization:
185
238
 
186
239
  ```python
187
- class ShortSyntax(Statica):
188
- name: str
240
+ class APIModel(Statica):
241
+ user_name: str = Field(
242
+ alias_for_parsing="userName",
243
+ alias_for_serialization="username"
244
+ )
245
+ user_id: int = Field(alias_for_parsing="userId")
246
+
247
+ # Parse from camelCase API response
248
+ api_data = {"userName": "alice", "userId": 123}
249
+ model = APIModel.from_map(api_data)
250
+
251
+ # Serialize to snake_case for internal use
252
+ internal_data = model.to_dict()
253
+ print(internal_data) # Output: {"username": "alice", "user_id": 123}
254
+ ```
189
255
 
190
- short = ShortSyntax(name="Test")
191
- print(short.name) # Output: "Test"
256
+ ### Alias Priority
257
+
258
+ When multiple alias types are defined, the priority is:
259
+ 1. `alias_for_parsing` for parsing operations
260
+ 2. `alias_for_serialization` for serialization operations
261
+ 3. `alias` as a fallback for both operations
262
+
263
+ ```python
264
+ class PriorityExample(Statica):
265
+ field_name: str = Field(
266
+ alias="generalAlias",
267
+ alias_for_parsing="parseAlias",
268
+ alias_for_serialization="serializeAlias"
269
+ )
270
+
271
+ # Uses alias_for_parsing
272
+ instance = PriorityExample.from_map({"parseAlias": "value"})
273
+
274
+ # Uses alias_for_serialization
275
+ result = instance.to_dict()
276
+ print(result) # Output: {"serializeAlias": "value"}
192
277
  ```
193
278
 
279
+ Advanced Usage
280
+ ----------------------------------------------------------------------------------------
281
+
194
282
  ### Custom Initialization
195
283
 
196
284
  Statica automatically generates an `__init__` method based on type annotations, ensuring that all required fields are provided during initialization.
@@ -207,7 +295,9 @@ instance = CastingExample(num="42")
207
295
  print(instance.num) # Output: 42
208
296
  ```
209
297
 
210
- ## Contributing
298
+
299
+ Contributing
300
+ ----------------------------------------------------------------------------------------
211
301
 
212
302
  We welcome contributions to Statica! To contribute:
213
303
 
@@ -216,11 +306,14 @@ We welcome contributions to Statica! To contribute:
216
306
  3. Write tests for your changes.
217
307
  4. Submit a pull request.
218
308
 
219
- ## License
309
+
310
+ License
311
+ ----------------------------------------------------------------------------------------
220
312
 
221
313
  Statica is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
222
314
 
223
- ## Acknowledgments
224
315
 
225
- Statica was built to simplify data validation and provide a robust and simple framework for type-safe models in Python, inspired by `pydantic` and `dataclasses`.
316
+ Acknowledgments
317
+ ----------------------------------------------------------------------------------------
226
318
 
319
+ Statica was built to simplify data validation and provide a robust and simple framework for type-safe models in Python, inspired by `pydantic` and `dataclasses`.
@@ -1,4 +1,5 @@
1
- # Statica
1
+ Statica
2
+ ========================================================================================
2
3
 
3
4
  ![Tests](https://github.com/mkrd/statica/actions/workflows/test.yml/badge.svg)
4
5
  ![Coverage](https://github.com/mkrd/statica/blob/main/assets/coverage.svg?raw=true)
@@ -6,17 +7,21 @@
6
7
 
7
8
  Statica is a Python library for defining and validating structured data with type annotations and constraints. It provides an easy-to-use framework for creating type-safe models with comprehensive validation for both types and constraints.
8
9
 
9
- ## Why Statica?
10
10
 
11
- Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like pydantic.
11
+ Why Statica?
12
+ ----------------------------------------------------------------------------------------
13
+
14
+ Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like Pydantic.
12
15
  While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
13
16
 
14
- 1. **Lightweight**: Statica does not rely on any third-party dependencies, making it ideal for projects where minimizing external dependencies is a priority.
17
+ 1. **Lightweight**: Statica has zero dependencies, making it ideal for projects where minimizing external dependencies is a priority.
15
18
  2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
16
- 3. **Ease of Use**: With its simple, Pythonic design, Statica is intuitive for developers already familiar with Python's `dataclasses` and type hinting. It avoids much of the magic that pydantic employs.
17
- 4. **Customizable Validation**: Statica allows fine-grained control over type and constraint validation through customizable field descriptors (`Field`) and error classes.
19
+ 3. **Ease of Use**: With its simple, Pythonic design, Statica is intuitive for developers already familiar with Python's `dataclasses` and type hinting. It avoids much of the magic and complexity of Pydantic.
20
+ 4. **Customizable**: Statica allows fine-grained control over type and constraint validation through customizable fields and error classes.
21
+
18
22
 
19
- ## Features
23
+ Features
24
+ ----------------------------------------------------------------------------------------
20
25
 
21
26
  - **Type Validation**: Automatically validates types for attributes based on type hints.
22
27
  - **Constraint Validation**: Define constraints like minimum/maximum length, value ranges, and more.
@@ -26,8 +31,11 @@ While pydantic is a powerful tool for data validation and parsing, Statica offer
26
31
  - **Automatic Initialization**: Automatically generate constructors (`__init__`) for your models.
27
32
  - **String Manipulation**: Strip whitespace from string fields if needed.
28
33
  - **Casting**: Automatically cast values to the desired type.
34
+ - **Field Aliasing**: Support for field aliases for parsing and serialization.
29
35
 
30
- ## Installation
36
+
37
+ Installation
38
+ ----------------------------------------------------------------------------------------
31
39
 
32
40
  You can install Statica via pip:
33
41
 
@@ -35,7 +43,9 @@ You can install Statica via pip:
35
43
  pip install statica
36
44
  ```
37
45
 
38
- ## Getting Started
46
+
47
+ Getting Started
48
+ ----------------------------------------------------------------------------------------
39
49
 
40
50
  ### Basic Usage
41
51
 
@@ -141,20 +151,98 @@ except CustomError as e:
141
151
  print(e) # Output: "num: must be at least 1"
142
152
  ```
143
153
 
144
- ## Advanced Usage
154
+ Or, define a BaseClass which configures the error classes globally:
155
+
156
+ ```python
157
+ from statica.core import Statica, ConstraintValidationError, TypeValidationError
158
+
159
+ class BaseClass(Statica):
160
+ constraint_error_class = ConstraintValidationError
161
+ type_error_class = TypeValidationError
162
+
163
+ class CustomPayload(BaseClass):
164
+ num: int = Field(min_value=1, max_value=10)
165
+
166
+ try:
167
+ payload = CustomPayload(num=0)
168
+ except ConstraintValidationError as e:
169
+ print(e) # Output: "num: must be at least 1"
170
+ ```
171
+
145
172
 
146
- ### Short Syntax for Fields
173
+ Aliasing
174
+ ----------------------------------------------------------------------------------------
147
175
 
148
- You can use simple type annotations without explicitly defining `Field` descriptors:
176
+ Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
177
+ This is particularly useful when working with external APIs that use different naming conventions.
178
+
179
+ ### Basic Aliases
180
+
181
+ Use the `alias` parameter to define an alternative name for both parsing and serialization:
182
+
183
+ ```python
184
+ class User(Statica):
185
+ full_name: str = Field(alias="fullName")
186
+ age: int = Field(alias="userAge")
187
+
188
+ # Parse data with aliases
189
+ data = {"fullName": "John Doe", "userAge": 30}
190
+ user = User.from_map(data)
191
+ print(user.full_name) # Output: "John Doe"
192
+ print(user.age) # Output: 30
193
+
194
+ # Serialize back with aliases
195
+ result = user.to_dict()
196
+ print(result) # Output: {"fullName": "John Doe", "userAge": 30}
197
+ ```
198
+
199
+ ### Separate Parsing and Serialization Aliases
200
+
201
+ You can define different aliases for parsing and serialization:
149
202
 
150
203
  ```python
151
- class ShortSyntax(Statica):
152
- name: str
204
+ class APIModel(Statica):
205
+ user_name: str = Field(
206
+ alias_for_parsing="userName",
207
+ alias_for_serialization="username"
208
+ )
209
+ user_id: int = Field(alias_for_parsing="userId")
210
+
211
+ # Parse from camelCase API response
212
+ api_data = {"userName": "alice", "userId": 123}
213
+ model = APIModel.from_map(api_data)
214
+
215
+ # Serialize to snake_case for internal use
216
+ internal_data = model.to_dict()
217
+ print(internal_data) # Output: {"username": "alice", "user_id": 123}
218
+ ```
153
219
 
154
- short = ShortSyntax(name="Test")
155
- print(short.name) # Output: "Test"
220
+ ### Alias Priority
221
+
222
+ When multiple alias types are defined, the priority is:
223
+ 1. `alias_for_parsing` for parsing operations
224
+ 2. `alias_for_serialization` for serialization operations
225
+ 3. `alias` as a fallback for both operations
226
+
227
+ ```python
228
+ class PriorityExample(Statica):
229
+ field_name: str = Field(
230
+ alias="generalAlias",
231
+ alias_for_parsing="parseAlias",
232
+ alias_for_serialization="serializeAlias"
233
+ )
234
+
235
+ # Uses alias_for_parsing
236
+ instance = PriorityExample.from_map({"parseAlias": "value"})
237
+
238
+ # Uses alias_for_serialization
239
+ result = instance.to_dict()
240
+ print(result) # Output: {"serializeAlias": "value"}
156
241
  ```
157
242
 
243
+ Advanced Usage
244
+ ----------------------------------------------------------------------------------------
245
+
158
246
  ### Custom Initialization
159
247
 
160
248
  Statica automatically generates an `__init__` method based on type annotations, ensuring that all required fields are provided during initialization.
@@ -171,7 +259,9 @@ instance = CastingExample(num="42")
171
259
  print(instance.num) # Output: 42
172
260
  ```
173
261
 
174
- ## Contributing
262
+
263
+ Contributing
264
+ ----------------------------------------------------------------------------------------
175
265
 
176
266
  We welcome contributions to Statica! To contribute:
177
267
 
@@ -180,11 +270,14 @@ We welcome contributions to Statica! To contribute:
180
270
  3. Write tests for your changes.
181
271
  4. Submit a pull request.
182
272
 
183
- ## License
273
+
274
+ License
275
+ ----------------------------------------------------------------------------------------
184
276
 
185
277
  Statica is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
186
278
 
187
- ## Acknowledgments
188
279
 
189
- Statica was built to simplify data validation and provide a robust and simple framework for type-safe models in Python, inspired by `pydantic` and `dataclasses`.
280
+ Acknowledgments
281
+ ----------------------------------------------------------------------------------------
190
282
 
283
+ Statica was built to simplify data validation and provide a robust and simple framework for type-safe models in Python, inspired by `pydantic` and `dataclasses`.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "statica"
3
- version = "1.2.0"
3
+ version = "1.3.0"
4
4
  description = "A minimalistic data validation library"
5
5
  readme = "README.md"
6
6
  authors = [{ name = "Marcel Kröker", email = "kroeker.marcel@gmail.com" }]
@@ -13,7 +13,7 @@ classifiers=[
13
13
  "Programming Language :: Python",
14
14
  "Topic :: Software Development :: Libraries :: Python Modules"
15
15
  ]
16
- requires-python = ">=3.10, <3.14"
16
+ requires-python = ">=3.11, <3.14"
17
17
 
18
18
 
19
19
  ########################################################################################
@@ -51,7 +51,7 @@ package = true
51
51
 
52
52
  [tool.ruff]
53
53
  show-fixes = true
54
- line-length = 120
54
+ line-length = 100
55
55
 
56
56
 
57
57
  [tool.ruff.lint]
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  from dataclasses import dataclass
4
4
  from dataclasses import field as dataclass_field
5
+ from types import UnionType
5
6
  from typing import (
6
7
  TYPE_CHECKING,
7
8
  Any,
@@ -11,11 +12,10 @@ from typing import (
11
12
  cast,
12
13
  dataclass_transform,
13
14
  get_type_hints,
14
- overload,
15
15
  )
16
16
 
17
17
  from statica.exceptions import ConstraintValidationError, TypeValidationError
18
- from statica.validators import validate_constraints, validate_type
18
+ from statica.validation import validate_constraints, validate_or_raise
19
19
 
20
20
  if TYPE_CHECKING:
21
21
  from collections.abc import Callable, Mapping
@@ -27,15 +27,53 @@ T = TypeVar("T")
27
27
  #### MARK: Field descriptor
28
28
 
29
29
 
30
- @dataclass
30
+ @dataclass(slots=True)
31
31
  class FieldDescriptor(Generic[T]):
32
32
  """
33
- Descriptor for validated fields.
33
+ Descriptor for statica fields. They will be used to validate fields:
34
+
35
+ Example:
36
+ .. code-block:: python
37
+ class User(Statica):
38
+ name: str # Will be initialized as FieldDescriptor during class creation
39
+ age: int | None = Field(min_value=0, max_value=120)
34
40
  """
35
41
 
36
- name: str = dataclass_field(init=False, repr=False)
37
- owner: type = dataclass_field(init=False, repr=False)
38
- expected_type: type = dataclass_field(init=False, repr=False)
42
+ # Descriptor attributes
43
+
44
+ name: str = dataclass_field(init=False)
45
+ """
46
+ Name of the field in the class.
47
+ Example: `"age"` for the field `age: int | None`.
48
+ """
49
+
50
+ owner: type = dataclass_field(init=False)
51
+ """
52
+ Owner class of the field descriptor.
53
+ Example: `<class '__main__.User'>` for the field `age: int | None`.
54
+ """
55
+
56
+ expected_type: Any = dataclass_field(init=False)
57
+ """
58
+ Expected type of the field.
59
+ Example 1: `int | None` (of type UnionType) for the field `age: int | None`.
60
+ Example 2: `<class 'int'>` for a field `number: int`.
61
+ """
62
+
63
+ sub_types: tuple[type, ...] = dataclass_field(init=False, default_factory=tuple)
64
+ """
65
+ List of types that expected_type is composed of.
66
+ If expected_type is not of type UnionType, then it is just a tuple with one element.
67
+ """
68
+
69
+ statica_sub_class: type[Statica] | None = dataclass_field(init=False, repr=False)
70
+ """
71
+ Statica subclass if the expected_type is a Statica subclass or a union where one
72
+ of the types is a Statica subclass.
73
+ For example, if expected_type is `Union[Statica, int]`, then this will be `Statica`.
74
+ """
75
+
76
+ # User-facing dataclass fields
39
77
 
40
78
  min_length: int | None = None
41
79
  max_length: int | None = None
@@ -51,44 +89,79 @@ class FieldDescriptor(Generic[T]):
51
89
  def __set_name__(self, owner: Any, name: str) -> None:
52
90
  self.name = name
53
91
  self.owner = owner
54
- self.expected_type = owner.__annotations__.get(name)
92
+ self.expected_type = get_type_hints(owner)[name]
93
+ self.sub_types = self.get_sub_types(self.expected_type)
94
+ self.statica_sub_class = self.get_statica_subclass(self.sub_types)
95
+
96
+ def get_sub_types(self, expected_type: Any) -> tuple[type | Any, ...]:
97
+ """
98
+ Get all subtypes of the expected type.
99
+ For example, if the expected type is Union[str, int], return [str, int].
100
+ """
101
+
102
+ if type(expected_type) is UnionType:
103
+ return tuple(t for t in expected_type.__args__)
55
104
 
56
- @overload
57
- def __get__(self, instance: None, owner: Any) -> FieldDescriptor[T]: ...
105
+ return (expected_type,)
58
106
 
59
- @overload
60
- def __get__(self, instance: object, owner: Any) -> T: ...
107
+ def get_statica_subclass(self, sub_types: tuple[type, ...]) -> type[Statica] | None:
108
+ """
109
+ Get the first Statica subclass from the sub_types.
110
+ Returns None if no Statica subclass is found.
111
+ """
112
+
113
+ try:
114
+ for sub_type in sub_types:
115
+ if issubclass(sub_type, Statica):
116
+ return sub_type
117
+ except TypeError:
118
+ pass
119
+ return None
61
120
 
62
121
  def __get__(self, instance: object | None, owner: Any) -> Any:
122
+ """
123
+ Get the value of the field from the instance.
124
+ For example user.age will invoke this method.
125
+ """
126
+
63
127
  if instance is None:
64
128
  return self # Accessed on the class, return the descriptor
65
129
  return instance.__dict__.get(self.name)
66
130
 
67
131
  def __set__(self, instance: object, value: T) -> None:
132
+ """
133
+ Set the value of the field on the instance.
134
+ For example user.age = 30 will invoke this method.
135
+ """
136
+
68
137
  instance.__dict__[self.name] = self.validate(value)
69
138
 
70
139
  def validate(self, value: Any) -> Any:
71
140
  try:
141
+ # (1/4) Cast to required type if cast_to is provided
142
+
72
143
  if self.cast_to is not None:
73
144
  value = self.cast_to(value)
74
145
 
75
- validate_type(value, self.expected_type)
76
-
77
- if (
78
- self.min_length is not None
79
- or self.max_length is not None
80
- or self.min_value is not None
81
- or self.max_value is not None
82
- or self.strip_whitespace is not None
83
- ):
84
- value = validate_constraints(
85
- value,
86
- min_length=self.min_length,
87
- max_length=self.max_length,
88
- min_value=self.min_value,
89
- max_value=self.max_value,
90
- strip_whitespace=self.strip_whitespace,
91
- )
146
+ # (2/4) On dict assignments initialize Statica subclass if it exists
147
+
148
+ if self.statica_sub_class is not None and isinstance(value, dict):
149
+ value = self.statica_sub_class.from_map(value)
150
+
151
+ # (3/4) Validate type of the value
152
+
153
+ validate_or_raise(value, self.expected_type)
154
+
155
+ # (4/4) Validate constraints if any are set
156
+
157
+ value = validate_constraints(
158
+ value,
159
+ min_length=self.min_length,
160
+ max_length=self.max_length,
161
+ min_value=self.min_value,
162
+ max_value=self.max_value,
163
+ strip_whitespace=self.strip_whitespace,
164
+ )
92
165
 
93
166
  except TypeValidationError as e:
94
167
  msg = f"{self.name}: {e!s}"
@@ -185,11 +258,7 @@ class StaticaMeta(type):
185
258
  # Generate custom __init__ method
186
259
 
187
260
  def statica_init(self: Statica, **kwargs: Any) -> None:
188
- for field_name, field_type in annotations.items():
189
- if field_name not in kwargs and not isinstance(None, field_type):
190
- msg = f"Missing required field: {field_name}"
191
- raise TypeValidationError(msg)
192
-
261
+ for field_name in annotations:
193
262
  setattr(self, field_name, kwargs.get(field_name))
194
263
 
195
264
  namespace["__init__"] = statica_init
@@ -218,23 +287,24 @@ class StaticaMeta(type):
218
287
  class Statica(metaclass=StaticaMeta):
219
288
  @classmethod
220
289
  def from_map(cls, mapping: Mapping[str, Any]) -> Self:
221
- mapping_key_to_field_keys = {} # Maps alias to field name
290
+ # Fields might have aliases, so we need to map them correctly.
291
+ # Here we map the chosen alias to the original field name.
292
+ # If no alias is provided, we use the field name itself.
293
+ # Priority: parsing alias > general alias > field name
294
+ mapping_key_to_field_keys = {}
222
295
 
223
296
  for field_descriptor in get_field_descriptors(cls):
224
297
  # Use alias for parsing if it exists
225
- alias = field_descriptor.alias_for_parsing or field_descriptor.alias or field_descriptor.name
298
+ alias = (
299
+ field_descriptor.alias_for_parsing
300
+ or field_descriptor.alias
301
+ or field_descriptor.name
302
+ )
226
303
  mapping_key_to_field_keys[alias] = field_descriptor.name
227
304
 
228
305
  parsed_mapping = {mapping_key_to_field_keys[k]: v for k, v in mapping.items()}
229
306
 
230
- instance = cls(**parsed_mapping)
231
-
232
- # Go through type hints and set values
233
- for attribute_name in get_type_hints(instance.__class__):
234
- value = parsed_mapping.get(attribute_name)
235
- setattr(instance, attribute_name, value) # Descriptor __set__ validates
236
-
237
- return instance
307
+ return cls(**parsed_mapping) # Init function will validate fields
238
308
 
239
309
  def to_dict(self) -> dict[str, Any]:
240
310
  """
@@ -243,7 +313,11 @@ class Statica(metaclass=StaticaMeta):
243
313
  result = {}
244
314
  for field_descriptor in get_field_descriptors(self.__class__):
245
315
  # Use alias for serialization if it exists
246
- alias = field_descriptor.alias_for_serialization or field_descriptor.alias or field_descriptor.name
316
+ alias = (
317
+ field_descriptor.alias_for_serialization
318
+ or field_descriptor.alias
319
+ or field_descriptor.name
320
+ )
247
321
  result[alias] = getattr(self, field_descriptor.name)
248
322
 
249
323
  return result
@@ -254,6 +328,11 @@ class Statica(metaclass=StaticaMeta):
254
328
 
255
329
  if __name__ == "__main__":
256
330
 
331
+ class User(Statica):
332
+ data: dict[str, int]
333
+
334
+ u = User.from_map({})
335
+
257
336
  class Payload(Statica):
258
337
  type_error_class = ValueError
259
338