statica 1.2.0__tar.gz → 1.4.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.
- {statica-1.2.0/statica.egg-info → statica-1.4.0}/PKG-INFO +170 -23
- statica-1.4.0/README.md +337 -0
- {statica-1.2.0 → statica-1.4.0}/pyproject.toml +3 -3
- statica-1.4.0/statica/config.py +33 -0
- statica-1.4.0/statica/core.py +414 -0
- statica-1.4.0/statica/validation.py +205 -0
- {statica-1.2.0 → statica-1.4.0/statica.egg-info}/PKG-INFO +170 -23
- {statica-1.2.0 → statica-1.4.0}/statica.egg-info/SOURCES.txt +8 -2
- {statica-1.2.0 → statica-1.4.0}/tests/test_aliasing.py +28 -48
- statica-1.4.0/tests/test_config.py +221 -0
- statica-1.4.0/tests/test_default_field.py +514 -0
- statica-1.4.0/tests/test_descriptor.py +38 -0
- statica-1.4.0/tests/test_python_behavior.py +39 -0
- {statica-1.2.0 → statica-1.4.0}/tests/test_statica.py +35 -0
- statica-1.4.0/tests/test_validate_or_raise.py +673 -0
- statica-1.4.0/tests/test_validation.py +100 -0
- statica-1.2.0/README.md +0 -190
- statica-1.2.0/statica/core.py +0 -279
- statica-1.2.0/statica/validators.py +0 -69
- statica-1.2.0/tests/test_validators.py +0 -24
- {statica-1.2.0 → statica-1.4.0}/LICENSE +0 -0
- {statica-1.2.0 → statica-1.4.0}/setup.cfg +0 -0
- {statica-1.2.0 → statica-1.4.0}/statica/__init__.py +0 -0
- {statica-1.2.0 → statica-1.4.0}/statica/exceptions.py +0 -0
- {statica-1.2.0 → statica-1.4.0}/statica.egg-info/dependency_links.txt +0 -0
- {statica-1.2.0 → statica-1.4.0}/statica.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statica
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.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.
|
|
34
|
+
Requires-Python: <3.14,>=3.11
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
Statica
|
|
38
|
+
========================================================================================
|
|
38
39
|
|
|
39
40
|

|
|
40
41
|

|
|
@@ -42,28 +43,36 @@ 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
|
|
47
|
+
Why Statica?
|
|
48
|
+
----------------------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to 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
|
|
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
|
|
53
|
-
4. **Customizable
|
|
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
|
-
|
|
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.
|
|
64
|
+
- **Default Field Values**: Set default values for fields that are used when not explicitly provided.
|
|
59
65
|
- **Customizable Error Handling**: Use custom exception classes for type and constraint errors.
|
|
60
66
|
- **Flexible Field Descriptors**: Add constraints, casting, and other behaviors to your fields.
|
|
61
67
|
- **Optional Fields**: Support for optional fields with default values.
|
|
62
68
|
- **Automatic Initialization**: Automatically generate constructors (`__init__`) for your models.
|
|
63
69
|
- **String Manipulation**: Strip whitespace from string fields if needed.
|
|
64
70
|
- **Casting**: Automatically cast values to the desired type.
|
|
71
|
+
- **Field Aliasing**: Support for field aliases for parsing and serialization.
|
|
65
72
|
|
|
66
|
-
|
|
73
|
+
|
|
74
|
+
Installation
|
|
75
|
+
----------------------------------------------------------------------------------------
|
|
67
76
|
|
|
68
77
|
You can install Statica via pip:
|
|
69
78
|
|
|
@@ -71,7 +80,9 @@ You can install Statica via pip:
|
|
|
71
80
|
pip install statica
|
|
72
81
|
```
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
|
|
84
|
+
Getting Started
|
|
85
|
+
----------------------------------------------------------------------------------------
|
|
75
86
|
|
|
76
87
|
### Basic Usage
|
|
77
88
|
|
|
@@ -142,13 +153,92 @@ payload = OptionalPayload()
|
|
|
142
153
|
print(payload.name) # Output: None
|
|
143
154
|
```
|
|
144
155
|
|
|
156
|
+
### Default Field Values
|
|
157
|
+
|
|
158
|
+
You can specify default values for fields in two ways:
|
|
159
|
+
|
|
160
|
+
#### Using Field() with default parameter
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
class User(Statica):
|
|
164
|
+
name: str
|
|
165
|
+
age: int = Field(default=25)
|
|
166
|
+
active: bool = Field(default=True)
|
|
167
|
+
|
|
168
|
+
# Using direct initialization
|
|
169
|
+
user = User(name="John")
|
|
170
|
+
print(user.name) # Output: "John"
|
|
171
|
+
print(user.age) # Output: 25
|
|
172
|
+
print(user.active) # Output: True
|
|
173
|
+
|
|
174
|
+
# Using from_map
|
|
175
|
+
user = User.from_map({"name": "Jane"})
|
|
176
|
+
print(user.age) # Output: 25 (default used)
|
|
177
|
+
|
|
178
|
+
# Explicit values override defaults
|
|
179
|
+
user = User(name="Bob", age=30, active=False)
|
|
180
|
+
print(user.age) # Output: 30
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
#### Direct assignment (without Field)
|
|
184
|
+
|
|
185
|
+
You can also assign default values directly to fields without using `Field()`:
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
class Config(Statica):
|
|
189
|
+
name: str
|
|
190
|
+
timeout: float = 30.0 # Direct assignment
|
|
191
|
+
retries: int = 3 # Direct assignment
|
|
192
|
+
debug: bool = False # Direct assignment
|
|
193
|
+
|
|
194
|
+
config = Config(name="server")
|
|
195
|
+
print(config.timeout) # Output: 30.0
|
|
196
|
+
print(config.retries) # Output: 3
|
|
197
|
+
print(config.debug) # Output: False
|
|
198
|
+
|
|
199
|
+
# Works with from_map too
|
|
200
|
+
config = Config.from_map({"name": "api-server"})
|
|
201
|
+
print(config.timeout) # Output: 30.0 (default used)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Both approaches work identically and can be mixed within the same class. Use `Field(default=...)` when you need additional constraints or options, and direct assignment for simple defaults.
|
|
205
|
+
|
|
206
|
+
#### Validation and Safety
|
|
207
|
+
|
|
208
|
+
Default values are validated against any constraints you've defined:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
class Config(Statica):
|
|
212
|
+
timeout: float = Field(default=30.0, min_value=1.0, max_value=120.0)
|
|
213
|
+
retries: int = Field(default=3, min_value=1)
|
|
214
|
+
|
|
215
|
+
config = Config() # Uses defaults: timeout=30.0, retries=3
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
For mutable default values (like lists, dicts, sets), Statica automatically creates copies to prevent shared state issues:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
class UserProfile(Statica):
|
|
222
|
+
name: str
|
|
223
|
+
tags: list[str] = Field(default=[]) # or tags: list[str] = []
|
|
224
|
+
|
|
225
|
+
user1 = UserProfile(name="Alice")
|
|
226
|
+
user2 = UserProfile(name="Bob")
|
|
227
|
+
|
|
228
|
+
user1.tags.append("admin")
|
|
229
|
+
print(user1.tags) # Output: ["admin"]
|
|
230
|
+
print(user2.tags) # Output: [] (not affected)
|
|
231
|
+
```
|
|
232
|
+
|
|
145
233
|
### Field Constraints
|
|
146
234
|
|
|
147
|
-
You can specify constraints on fields:
|
|
235
|
+
You can specify constraints and options on fields:
|
|
148
236
|
|
|
237
|
+
- **Default Values**: `default` (using `Field()`) or direct assignment
|
|
149
238
|
- **String Constraints**: `min_length`, `max_length`, `strip_whitespace`
|
|
150
239
|
- **Numeric Constraints**: `min_value`, `max_value`
|
|
151
240
|
- **Casting**: `cast_to`
|
|
241
|
+
- **Aliasing**: `alias`
|
|
152
242
|
|
|
153
243
|
```python
|
|
154
244
|
class StringTest(Statica):
|
|
@@ -156,6 +246,15 @@ class StringTest(Statica):
|
|
|
156
246
|
|
|
157
247
|
class IntTest(Statica):
|
|
158
248
|
num: int = Field(min_value=1, max_value=10, cast_to=int)
|
|
249
|
+
|
|
250
|
+
class DefaultTest(Statica):
|
|
251
|
+
# Using Field() for defaults with constraints
|
|
252
|
+
status: str = Field(default="active")
|
|
253
|
+
priority: int = Field(default=1, min_value=1, max_value=5)
|
|
254
|
+
|
|
255
|
+
# Direct assignment for simple defaults
|
|
256
|
+
timeout: float = 30.0
|
|
257
|
+
retries: int = 3
|
|
159
258
|
```
|
|
160
259
|
|
|
161
260
|
### Custom Error Classes
|
|
@@ -177,20 +276,57 @@ except CustomError as e:
|
|
|
177
276
|
print(e) # Output: "num: must be at least 1"
|
|
178
277
|
```
|
|
179
278
|
|
|
180
|
-
|
|
279
|
+
Or, define a BaseClass which configures the error classes globally:
|
|
280
|
+
|
|
281
|
+
```python
|
|
282
|
+
from statica.core import Statica, ConstraintValidationError, TypeValidationError
|
|
283
|
+
|
|
284
|
+
class BaseClass(Statica):
|
|
285
|
+
constraint_error_class = ConstraintValidationError
|
|
286
|
+
type_error_class = TypeValidationError
|
|
287
|
+
|
|
288
|
+
class CustomPayload(BaseClass):
|
|
289
|
+
num: int = Field(min_value=1, max_value=10)
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
payload = CustomPayload(num=0)
|
|
293
|
+
except ConstraintValidationError as e:
|
|
294
|
+
print(e) # Output: "num: must be at least 1"
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
Aliasing
|
|
299
|
+
----------------------------------------------------------------------------------------
|
|
181
300
|
|
|
182
|
-
|
|
301
|
+
Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
|
|
302
|
+
This is particularly useful when working with external APIs that use different naming conventions.
|
|
183
303
|
|
|
184
|
-
|
|
304
|
+
Use the `alias` parameter to define an alternative name for both parsing and serialization:
|
|
185
305
|
|
|
186
306
|
```python
|
|
187
|
-
class
|
|
188
|
-
|
|
307
|
+
class User(Statica):
|
|
308
|
+
full_name: str = Field(alias="fullName")
|
|
309
|
+
age: int = Field(alias="userAge", default=25)
|
|
310
|
+
|
|
311
|
+
# Parse data with aliases
|
|
312
|
+
data = {"fullName": "John Doe"} # userAge not provided, uses default
|
|
313
|
+
user = User.from_map(data)
|
|
314
|
+
print(user.full_name) # Output: "John Doe"
|
|
315
|
+
print(user.age) # Output: 25
|
|
316
|
+
|
|
317
|
+
# Serialize back with aliases (uses the alias for serialization by default)
|
|
318
|
+
result = user.to_dict()
|
|
319
|
+
print(result) # Output: {"fullName": "John Doe", "userAge": 25}
|
|
320
|
+
|
|
321
|
+
# Serialize without aliases
|
|
322
|
+
result_no_alias = user.to_dict(with_aliases=False)
|
|
323
|
+
print(result_no_alias) # Output: {"full_name": "John Doe", "age": 25}
|
|
189
324
|
|
|
190
|
-
short = ShortSyntax(name="Test")
|
|
191
|
-
print(short.name) # Output: "Test"
|
|
192
325
|
```
|
|
193
326
|
|
|
327
|
+
Advanced Usage
|
|
328
|
+
----------------------------------------------------------------------------------------
|
|
329
|
+
|
|
194
330
|
### Custom Initialization
|
|
195
331
|
|
|
196
332
|
Statica automatically generates an `__init__` method based on type annotations, ensuring that all required fields are provided during initialization.
|
|
@@ -207,7 +343,15 @@ instance = CastingExample(num="42")
|
|
|
207
343
|
print(instance.num) # Output: 42
|
|
208
344
|
```
|
|
209
345
|
|
|
210
|
-
|
|
346
|
+
|
|
347
|
+
Design Decisions
|
|
348
|
+
----------------------------------------------------------------------------------------
|
|
349
|
+
|
|
350
|
+
- [Inheritance over Decorators](docs/decision-inheritance-over-decorators.md)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
Contributing
|
|
354
|
+
----------------------------------------------------------------------------------------
|
|
211
355
|
|
|
212
356
|
We welcome contributions to Statica! To contribute:
|
|
213
357
|
|
|
@@ -216,11 +360,14 @@ We welcome contributions to Statica! To contribute:
|
|
|
216
360
|
3. Write tests for your changes.
|
|
217
361
|
4. Submit a pull request.
|
|
218
362
|
|
|
219
|
-
|
|
363
|
+
|
|
364
|
+
License
|
|
365
|
+
----------------------------------------------------------------------------------------
|
|
220
366
|
|
|
221
367
|
Statica is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
222
368
|
|
|
223
|
-
## Acknowledgments
|
|
224
369
|
|
|
225
|
-
|
|
370
|
+
Acknowledgments
|
|
371
|
+
----------------------------------------------------------------------------------------
|
|
226
372
|
|
|
373
|
+
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`.
|
statica-1.4.0/README.md
ADDED
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
Statica
|
|
2
|
+
========================================================================================
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
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.
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
Why Statica?
|
|
12
|
+
----------------------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to Pydantic.
|
|
15
|
+
While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
|
|
16
|
+
|
|
17
|
+
1. **Lightweight**: Statica has zero dependencies, making it ideal for projects where minimizing external dependencies is a priority.
|
|
18
|
+
2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
|
|
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
|
+
|
|
22
|
+
|
|
23
|
+
Features
|
|
24
|
+
----------------------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
- **Type Validation**: Automatically validates types for attributes based on type hints.
|
|
27
|
+
- **Constraint Validation**: Define constraints like minimum/maximum length, value ranges, and more.
|
|
28
|
+
- **Default Field Values**: Set default values for fields that are used when not explicitly provided.
|
|
29
|
+
- **Customizable Error Handling**: Use custom exception classes for type and constraint errors.
|
|
30
|
+
- **Flexible Field Descriptors**: Add constraints, casting, and other behaviors to your fields.
|
|
31
|
+
- **Optional Fields**: Support for optional fields with default values.
|
|
32
|
+
- **Automatic Initialization**: Automatically generate constructors (`__init__`) for your models.
|
|
33
|
+
- **String Manipulation**: Strip whitespace from string fields if needed.
|
|
34
|
+
- **Casting**: Automatically cast values to the desired type.
|
|
35
|
+
- **Field Aliasing**: Support for field aliases for parsing and serialization.
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Installation
|
|
39
|
+
----------------------------------------------------------------------------------------
|
|
40
|
+
|
|
41
|
+
You can install Statica via pip:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install statica
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
Getting Started
|
|
49
|
+
----------------------------------------------------------------------------------------
|
|
50
|
+
|
|
51
|
+
### Basic Usage
|
|
52
|
+
|
|
53
|
+
Define a model with type annotations and constraints:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from statica.core import Statica, Field
|
|
57
|
+
|
|
58
|
+
class Payload(Statica):
|
|
59
|
+
name: str = Field(min_length=3, max_length=50, strip_whitespace=True)
|
|
60
|
+
description: str | None = Field(max_length=200)
|
|
61
|
+
num: int | float
|
|
62
|
+
float_num: float | None
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Instantiate the model using a dictionary:
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
data = {
|
|
69
|
+
"name": "Test Payload",
|
|
70
|
+
"description": "A short description.",
|
|
71
|
+
"num": 42,
|
|
72
|
+
"float_num": 3.14,
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
payload = Payload.from_map(data)
|
|
76
|
+
print(payload.name) # Output: "Test Payload"
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Or instantiate directly:
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
payload = Payload(
|
|
83
|
+
name="Test",
|
|
84
|
+
description="This is a test description.",
|
|
85
|
+
num=42,
|
|
86
|
+
float_num=3.14,
|
|
87
|
+
)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Validation
|
|
91
|
+
|
|
92
|
+
Statica automatically validates attributes based on type annotations and constraints:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from statica.core import ConstraintValidationError, TypeValidationError
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
payload = Payload(name="Te", description="Valid", num=42)
|
|
99
|
+
except ConstraintValidationError as e:
|
|
100
|
+
print(e) # Output: "name: length must be at least 3"
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
payload = Payload(name="Test", description="Valid", num="Invalid")
|
|
104
|
+
except TypeValidationError as e:
|
|
105
|
+
print(e) # Output: "num: expected type 'int | float', got 'str'"
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Optional Fields
|
|
109
|
+
|
|
110
|
+
Fields annotated with `| None` are optional and default to `None`:
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
class OptionalPayload(Statica):
|
|
114
|
+
name: str | None
|
|
115
|
+
|
|
116
|
+
payload = OptionalPayload()
|
|
117
|
+
print(payload.name) # Output: None
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Default Field Values
|
|
121
|
+
|
|
122
|
+
You can specify default values for fields in two ways:
|
|
123
|
+
|
|
124
|
+
#### Using Field() with default parameter
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
class User(Statica):
|
|
128
|
+
name: str
|
|
129
|
+
age: int = Field(default=25)
|
|
130
|
+
active: bool = Field(default=True)
|
|
131
|
+
|
|
132
|
+
# Using direct initialization
|
|
133
|
+
user = User(name="John")
|
|
134
|
+
print(user.name) # Output: "John"
|
|
135
|
+
print(user.age) # Output: 25
|
|
136
|
+
print(user.active) # Output: True
|
|
137
|
+
|
|
138
|
+
# Using from_map
|
|
139
|
+
user = User.from_map({"name": "Jane"})
|
|
140
|
+
print(user.age) # Output: 25 (default used)
|
|
141
|
+
|
|
142
|
+
# Explicit values override defaults
|
|
143
|
+
user = User(name="Bob", age=30, active=False)
|
|
144
|
+
print(user.age) # Output: 30
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
#### Direct assignment (without Field)
|
|
148
|
+
|
|
149
|
+
You can also assign default values directly to fields without using `Field()`:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
class Config(Statica):
|
|
153
|
+
name: str
|
|
154
|
+
timeout: float = 30.0 # Direct assignment
|
|
155
|
+
retries: int = 3 # Direct assignment
|
|
156
|
+
debug: bool = False # Direct assignment
|
|
157
|
+
|
|
158
|
+
config = Config(name="server")
|
|
159
|
+
print(config.timeout) # Output: 30.0
|
|
160
|
+
print(config.retries) # Output: 3
|
|
161
|
+
print(config.debug) # Output: False
|
|
162
|
+
|
|
163
|
+
# Works with from_map too
|
|
164
|
+
config = Config.from_map({"name": "api-server"})
|
|
165
|
+
print(config.timeout) # Output: 30.0 (default used)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both approaches work identically and can be mixed within the same class. Use `Field(default=...)` when you need additional constraints or options, and direct assignment for simple defaults.
|
|
169
|
+
|
|
170
|
+
#### Validation and Safety
|
|
171
|
+
|
|
172
|
+
Default values are validated against any constraints you've defined:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
class Config(Statica):
|
|
176
|
+
timeout: float = Field(default=30.0, min_value=1.0, max_value=120.0)
|
|
177
|
+
retries: int = Field(default=3, min_value=1)
|
|
178
|
+
|
|
179
|
+
config = Config() # Uses defaults: timeout=30.0, retries=3
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
For mutable default values (like lists, dicts, sets), Statica automatically creates copies to prevent shared state issues:
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
class UserProfile(Statica):
|
|
186
|
+
name: str
|
|
187
|
+
tags: list[str] = Field(default=[]) # or tags: list[str] = []
|
|
188
|
+
|
|
189
|
+
user1 = UserProfile(name="Alice")
|
|
190
|
+
user2 = UserProfile(name="Bob")
|
|
191
|
+
|
|
192
|
+
user1.tags.append("admin")
|
|
193
|
+
print(user1.tags) # Output: ["admin"]
|
|
194
|
+
print(user2.tags) # Output: [] (not affected)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Field Constraints
|
|
198
|
+
|
|
199
|
+
You can specify constraints and options on fields:
|
|
200
|
+
|
|
201
|
+
- **Default Values**: `default` (using `Field()`) or direct assignment
|
|
202
|
+
- **String Constraints**: `min_length`, `max_length`, `strip_whitespace`
|
|
203
|
+
- **Numeric Constraints**: `min_value`, `max_value`
|
|
204
|
+
- **Casting**: `cast_to`
|
|
205
|
+
- **Aliasing**: `alias`
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
class StringTest(Statica):
|
|
209
|
+
name: str = Field(min_length=3, max_length=5, strip_whitespace=True)
|
|
210
|
+
|
|
211
|
+
class IntTest(Statica):
|
|
212
|
+
num: int = Field(min_value=1, max_value=10, cast_to=int)
|
|
213
|
+
|
|
214
|
+
class DefaultTest(Statica):
|
|
215
|
+
# Using Field() for defaults with constraints
|
|
216
|
+
status: str = Field(default="active")
|
|
217
|
+
priority: int = Field(default=1, min_value=1, max_value=5)
|
|
218
|
+
|
|
219
|
+
# Direct assignment for simple defaults
|
|
220
|
+
timeout: float = 30.0
|
|
221
|
+
retries: int = 3
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Custom Error Classes
|
|
225
|
+
|
|
226
|
+
You can define custom error classes for type and constraint validation:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
class CustomError(Exception):
|
|
230
|
+
pass
|
|
231
|
+
|
|
232
|
+
class CustomPayload(Statica):
|
|
233
|
+
constraint_error_class = CustomError
|
|
234
|
+
|
|
235
|
+
num: int = Field(min_value=1, max_value=10)
|
|
236
|
+
|
|
237
|
+
try:
|
|
238
|
+
payload = CustomPayload(num=0)
|
|
239
|
+
except CustomError as e:
|
|
240
|
+
print(e) # Output: "num: must be at least 1"
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Or, define a BaseClass which configures the error classes globally:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from statica.core import Statica, ConstraintValidationError, TypeValidationError
|
|
247
|
+
|
|
248
|
+
class BaseClass(Statica):
|
|
249
|
+
constraint_error_class = ConstraintValidationError
|
|
250
|
+
type_error_class = TypeValidationError
|
|
251
|
+
|
|
252
|
+
class CustomPayload(BaseClass):
|
|
253
|
+
num: int = Field(min_value=1, max_value=10)
|
|
254
|
+
|
|
255
|
+
try:
|
|
256
|
+
payload = CustomPayload(num=0)
|
|
257
|
+
except ConstraintValidationError as e:
|
|
258
|
+
print(e) # Output: "num: must be at least 1"
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
Aliasing
|
|
263
|
+
----------------------------------------------------------------------------------------
|
|
264
|
+
|
|
265
|
+
Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
|
|
266
|
+
This is particularly useful when working with external APIs that use different naming conventions.
|
|
267
|
+
|
|
268
|
+
Use the `alias` parameter to define an alternative name for both parsing and serialization:
|
|
269
|
+
|
|
270
|
+
```python
|
|
271
|
+
class User(Statica):
|
|
272
|
+
full_name: str = Field(alias="fullName")
|
|
273
|
+
age: int = Field(alias="userAge", default=25)
|
|
274
|
+
|
|
275
|
+
# Parse data with aliases
|
|
276
|
+
data = {"fullName": "John Doe"} # userAge not provided, uses default
|
|
277
|
+
user = User.from_map(data)
|
|
278
|
+
print(user.full_name) # Output: "John Doe"
|
|
279
|
+
print(user.age) # Output: 25
|
|
280
|
+
|
|
281
|
+
# Serialize back with aliases (uses the alias for serialization by default)
|
|
282
|
+
result = user.to_dict()
|
|
283
|
+
print(result) # Output: {"fullName": "John Doe", "userAge": 25}
|
|
284
|
+
|
|
285
|
+
# Serialize without aliases
|
|
286
|
+
result_no_alias = user.to_dict(with_aliases=False)
|
|
287
|
+
print(result_no_alias) # Output: {"full_name": "John Doe", "age": 25}
|
|
288
|
+
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Advanced Usage
|
|
292
|
+
----------------------------------------------------------------------------------------
|
|
293
|
+
|
|
294
|
+
### Custom Initialization
|
|
295
|
+
|
|
296
|
+
Statica automatically generates an `__init__` method based on type annotations, ensuring that all required fields are provided during initialization.
|
|
297
|
+
|
|
298
|
+
### Casting
|
|
299
|
+
|
|
300
|
+
You can automatically cast input values to the desired type:
|
|
301
|
+
|
|
302
|
+
```python
|
|
303
|
+
class CastingExample(Statica):
|
|
304
|
+
num: int = Field(cast_to=int)
|
|
305
|
+
|
|
306
|
+
instance = CastingExample(num="42")
|
|
307
|
+
print(instance.num) # Output: 42
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
Design Decisions
|
|
312
|
+
----------------------------------------------------------------------------------------
|
|
313
|
+
|
|
314
|
+
- [Inheritance over Decorators](docs/decision-inheritance-over-decorators.md)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
Contributing
|
|
318
|
+
----------------------------------------------------------------------------------------
|
|
319
|
+
|
|
320
|
+
We welcome contributions to Statica! To contribute:
|
|
321
|
+
|
|
322
|
+
1. Fork the repository.
|
|
323
|
+
2. Create a new branch for your feature or bug fix.
|
|
324
|
+
3. Write tests for your changes.
|
|
325
|
+
4. Submit a pull request.
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
License
|
|
329
|
+
----------------------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
Statica is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
Acknowledgments
|
|
335
|
+
----------------------------------------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
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.
|
|
3
|
+
version = "1.4.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.
|
|
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 =
|
|
54
|
+
line-length = 100
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
[tool.ruff.lint]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class StaticaConfig:
|
|
8
|
+
type_error_message: str
|
|
9
|
+
min_length_error_message: str
|
|
10
|
+
max_length_error_message: str
|
|
11
|
+
min_value_error_message: str
|
|
12
|
+
max_value_error_message: str
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def create(
|
|
16
|
+
cls,
|
|
17
|
+
*,
|
|
18
|
+
type_error_message: str = "expected type '{expected_type}', got '{found_type}'",
|
|
19
|
+
min_length_error_message: str = "{field_name} length must be at least {min_length}",
|
|
20
|
+
max_length_error_message: str = "{field_name} length must be at most {max_length}",
|
|
21
|
+
min_value_error_message: str = "{field_name} must be at least {min_value}",
|
|
22
|
+
max_value_error_message: str = "{field_name} must be at most {max_value}",
|
|
23
|
+
) -> StaticaConfig:
|
|
24
|
+
return cls(
|
|
25
|
+
type_error_message=type_error_message,
|
|
26
|
+
min_length_error_message=min_length_error_message,
|
|
27
|
+
max_length_error_message=max_length_error_message,
|
|
28
|
+
min_value_error_message=min_value_error_message,
|
|
29
|
+
max_value_error_message=max_value_error_message,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
default_config = StaticaConfig.create()
|