statica 1.3.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: statica
3
- Version: 1.3.0
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
@@ -47,7 +47,7 @@ Statica is a Python library for defining and validating structured data with typ
47
47
  Why Statica?
48
48
  ----------------------------------------------------------------------------------------
49
49
 
50
- Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like Pydantic.
50
+ Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to Pydantic.
51
51
  While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
52
52
 
53
53
  1. **Lightweight**: Statica has zero dependencies, making it ideal for projects where minimizing external dependencies is a priority.
@@ -61,6 +61,7 @@ Features
61
61
 
62
62
  - **Type Validation**: Automatically validates types for attributes based on type hints.
63
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.
64
65
  - **Customizable Error Handling**: Use custom exception classes for type and constraint errors.
65
66
  - **Flexible Field Descriptors**: Add constraints, casting, and other behaviors to your fields.
66
67
  - **Optional Fields**: Support for optional fields with default values.
@@ -152,13 +153,92 @@ payload = OptionalPayload()
152
153
  print(payload.name) # Output: None
153
154
  ```
154
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
+
155
233
  ### Field Constraints
156
234
 
157
- You can specify constraints on fields:
235
+ You can specify constraints and options on fields:
158
236
 
237
+ - **Default Values**: `default` (using `Field()`) or direct assignment
159
238
  - **String Constraints**: `min_length`, `max_length`, `strip_whitespace`
160
239
  - **Numeric Constraints**: `min_value`, `max_value`
161
240
  - **Casting**: `cast_to`
241
+ - **Aliasing**: `alias`
162
242
 
163
243
  ```python
164
244
  class StringTest(Statica):
@@ -166,6 +246,15 @@ class StringTest(Statica):
166
246
 
167
247
  class IntTest(Statica):
168
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
169
258
  ```
170
259
 
171
260
  ### Custom Error Classes
@@ -212,68 +301,27 @@ Aliasing
212
301
  Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
213
302
  This is particularly useful when working with external APIs that use different naming conventions.
214
303
 
215
- ### Basic Aliases
216
-
217
304
  Use the `alias` parameter to define an alternative name for both parsing and serialization:
218
305
 
219
306
  ```python
220
307
  class User(Statica):
221
308
  full_name: str = Field(alias="fullName")
222
- age: int = Field(alias="userAge")
309
+ age: int = Field(alias="userAge", default=25)
223
310
 
224
311
  # Parse data with aliases
225
- data = {"fullName": "John Doe", "userAge": 30}
312
+ data = {"fullName": "John Doe"} # userAge not provided, uses default
226
313
  user = User.from_map(data)
227
314
  print(user.full_name) # Output: "John Doe"
228
- print(user.age) # Output: 30
315
+ print(user.age) # Output: 25
229
316
 
230
- # Serialize back with aliases
317
+ # Serialize back with aliases (uses the alias for serialization by default)
231
318
  result = user.to_dict()
232
- print(result) # Output: {"fullName": "John Doe", "userAge": 30}
233
- ```
319
+ print(result) # Output: {"fullName": "John Doe", "userAge": 25}
234
320
 
235
- ### Separate Parsing and Serialization Aliases
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}
236
324
 
237
- You can define different aliases for parsing and serialization:
238
-
239
- ```python
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
- ```
255
-
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"}
277
325
  ```
278
326
 
279
327
  Advanced Usage
@@ -296,6 +344,12 @@ print(instance.num) # Output: 42
296
344
  ```
297
345
 
298
346
 
347
+ Design Decisions
348
+ ----------------------------------------------------------------------------------------
349
+
350
+ - [Inheritance over Decorators](docs/decision-inheritance-over-decorators.md)
351
+
352
+
299
353
  Contributing
300
354
  ----------------------------------------------------------------------------------------
301
355
 
@@ -11,7 +11,7 @@ Statica is a Python library for defining and validating structured data with typ
11
11
  Why Statica?
12
12
  ----------------------------------------------------------------------------------------
13
13
 
14
- Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like Pydantic.
14
+ Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to Pydantic.
15
15
  While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
16
16
 
17
17
  1. **Lightweight**: Statica has zero dependencies, making it ideal for projects where minimizing external dependencies is a priority.
@@ -25,6 +25,7 @@ Features
25
25
 
26
26
  - **Type Validation**: Automatically validates types for attributes based on type hints.
27
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.
28
29
  - **Customizable Error Handling**: Use custom exception classes for type and constraint errors.
29
30
  - **Flexible Field Descriptors**: Add constraints, casting, and other behaviors to your fields.
30
31
  - **Optional Fields**: Support for optional fields with default values.
@@ -116,13 +117,92 @@ payload = OptionalPayload()
116
117
  print(payload.name) # Output: None
117
118
  ```
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
+
119
197
  ### Field Constraints
120
198
 
121
- You can specify constraints on fields:
199
+ You can specify constraints and options on fields:
122
200
 
201
+ - **Default Values**: `default` (using `Field()`) or direct assignment
123
202
  - **String Constraints**: `min_length`, `max_length`, `strip_whitespace`
124
203
  - **Numeric Constraints**: `min_value`, `max_value`
125
204
  - **Casting**: `cast_to`
205
+ - **Aliasing**: `alias`
126
206
 
127
207
  ```python
128
208
  class StringTest(Statica):
@@ -130,6 +210,15 @@ class StringTest(Statica):
130
210
 
131
211
  class IntTest(Statica):
132
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
133
222
  ```
134
223
 
135
224
  ### Custom Error Classes
@@ -176,68 +265,27 @@ Aliasing
176
265
  Statica supports field aliasing, allowing you to map different field names for parsing and serialization.
177
266
  This is particularly useful when working with external APIs that use different naming conventions.
178
267
 
179
- ### Basic Aliases
180
-
181
268
  Use the `alias` parameter to define an alternative name for both parsing and serialization:
182
269
 
183
270
  ```python
184
271
  class User(Statica):
185
272
  full_name: str = Field(alias="fullName")
186
- age: int = Field(alias="userAge")
273
+ age: int = Field(alias="userAge", default=25)
187
274
 
188
275
  # Parse data with aliases
189
- data = {"fullName": "John Doe", "userAge": 30}
276
+ data = {"fullName": "John Doe"} # userAge not provided, uses default
190
277
  user = User.from_map(data)
191
278
  print(user.full_name) # Output: "John Doe"
192
- print(user.age) # Output: 30
279
+ print(user.age) # Output: 25
193
280
 
194
- # Serialize back with aliases
281
+ # Serialize back with aliases (uses the alias for serialization by default)
195
282
  result = user.to_dict()
196
- print(result) # Output: {"fullName": "John Doe", "userAge": 30}
197
- ```
283
+ print(result) # Output: {"fullName": "John Doe", "userAge": 25}
198
284
 
199
- ### Separate Parsing and Serialization Aliases
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}
200
288
 
201
- You can define different aliases for parsing and serialization:
202
-
203
- ```python
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
- ```
219
-
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"}
241
289
  ```
242
290
 
243
291
  Advanced Usage
@@ -260,6 +308,12 @@ print(instance.num) # Output: 42
260
308
  ```
261
309
 
262
310
 
311
+ Design Decisions
312
+ ----------------------------------------------------------------------------------------
313
+
314
+ - [Inheritance over Decorators](docs/decision-inheritance-over-decorators.md)
315
+
316
+
263
317
  Contributing
264
318
  ----------------------------------------------------------------------------------------
265
319
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "statica"
3
- version = "1.3.0"
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" }]
@@ -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()