statica 1.1.0__tar.gz → 1.2.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.1.0/statica.egg-info → statica-1.2.0}/PKG-INFO +5 -5
- {statica-1.1.0 → statica-1.2.0}/README.md +3 -3
- {statica-1.1.0 → statica-1.2.0}/pyproject.toml +6 -2
- {statica-1.1.0 → statica-1.2.0}/statica/core.py +13 -16
- {statica-1.1.0 → statica-1.2.0}/statica/validators.py +5 -5
- {statica-1.1.0 → statica-1.2.0/statica.egg-info}/PKG-INFO +5 -5
- {statica-1.1.0 → statica-1.2.0}/statica.egg-info/SOURCES.txt +0 -2
- {statica-1.1.0 → statica-1.2.0}/tests/test_statica.py +1 -14
- {statica-1.1.0 → statica-1.2.0}/tests/test_validators.py +0 -13
- statica-1.1.0/statica/types_utils.py +0 -64
- statica-1.1.0/tests/test_types_utils.py +0 -33
- {statica-1.1.0 → statica-1.2.0}/LICENSE +0 -0
- {statica-1.1.0 → statica-1.2.0}/setup.cfg +0 -0
- {statica-1.1.0 → statica-1.2.0}/statica/__init__.py +0 -0
- {statica-1.1.0 → statica-1.2.0}/statica/exceptions.py +0 -0
- {statica-1.1.0 → statica-1.2.0}/statica.egg-info/dependency_links.txt +0 -0
- {statica-1.1.0 → statica-1.2.0}/statica.egg-info/top_level.txt +0 -0
- {statica-1.1.0 → statica-1.2.0}/tests/test_aliasing.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statica
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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,7 +31,7 @@ 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.10
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
|
|
37
37
|
# Statica
|
|
@@ -44,13 +44,13 @@ Statica is a Python library for defining and validating structured data with typ
|
|
|
44
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
|
+
Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like pydantic.
|
|
48
48
|
While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
|
|
49
49
|
|
|
50
50
|
1. **Lightweight**: Statica does not rely on any third-party dependencies, making it ideal for projects where minimizing external dependencies is a priority.
|
|
51
|
-
2. **
|
|
51
|
+
2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
|
|
52
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. **
|
|
53
|
+
4. **Customizable Validation**: Statica allows fine-grained control over type and constraint validation through customizable field descriptors (`Field`) and error classes.
|
|
54
54
|
|
|
55
55
|
## Features
|
|
56
56
|
|
|
@@ -8,13 +8,13 @@ Statica is a Python library for defining and validating structured data with typ
|
|
|
8
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
|
+
Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like pydantic.
|
|
12
12
|
While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
|
|
13
13
|
|
|
14
14
|
1. **Lightweight**: Statica does not rely on any third-party dependencies, making it ideal for projects where minimizing external dependencies is a priority.
|
|
15
|
-
2. **
|
|
15
|
+
2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
|
|
16
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. **
|
|
17
|
+
4. **Customizable Validation**: Statica allows fine-grained control over type and constraint validation through customizable field descriptors (`Field`) and error classes.
|
|
18
18
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "statica"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.2.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.10, <3.14"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
########################################################################################
|
|
@@ -26,6 +26,7 @@ dependencies = []
|
|
|
26
26
|
[dependency-groups]
|
|
27
27
|
dev = [
|
|
28
28
|
"mypy ~= 1.16.0",
|
|
29
|
+
"pyinstrument>=5.0.2",
|
|
29
30
|
"pytest-cov ~= 6.1.0",
|
|
30
31
|
"ruff ~= 0.11.0",
|
|
31
32
|
]
|
|
@@ -77,6 +78,9 @@ quote-style = "double"
|
|
|
77
78
|
#### MARK: Testing
|
|
78
79
|
|
|
79
80
|
[tool.coverage.report]
|
|
81
|
+
omit = [
|
|
82
|
+
"benchmark_*.py",
|
|
83
|
+
]
|
|
80
84
|
exclude_also = [
|
|
81
85
|
"if TYPE_CHECKING:",
|
|
82
86
|
'if __name__ == "__main__":',
|
|
@@ -2,7 +2,6 @@ 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
|
|
6
5
|
from typing import (
|
|
7
6
|
TYPE_CHECKING,
|
|
8
7
|
Any,
|
|
@@ -11,14 +10,11 @@ from typing import (
|
|
|
11
10
|
TypeVar,
|
|
12
11
|
cast,
|
|
13
12
|
dataclass_transform,
|
|
14
|
-
get_args,
|
|
15
|
-
get_origin,
|
|
16
13
|
get_type_hints,
|
|
17
14
|
overload,
|
|
18
15
|
)
|
|
19
16
|
|
|
20
17
|
from statica.exceptions import ConstraintValidationError, TypeValidationError
|
|
21
|
-
from statica.types_utils import get_expected_type
|
|
22
18
|
from statica.validators import validate_constraints, validate_type
|
|
23
19
|
|
|
24
20
|
if TYPE_CHECKING:
|
|
@@ -55,7 +51,7 @@ class FieldDescriptor(Generic[T]):
|
|
|
55
51
|
def __set_name__(self, owner: Any, name: str) -> None:
|
|
56
52
|
self.name = name
|
|
57
53
|
self.owner = owner
|
|
58
|
-
self.expected_type =
|
|
54
|
+
self.expected_type = owner.__annotations__.get(name)
|
|
59
55
|
|
|
60
56
|
@overload
|
|
61
57
|
def __get__(self, instance: None, owner: Any) -> FieldDescriptor[T]: ...
|
|
@@ -78,7 +74,13 @@ class FieldDescriptor(Generic[T]):
|
|
|
78
74
|
|
|
79
75
|
validate_type(value, self.expected_type)
|
|
80
76
|
|
|
81
|
-
if
|
|
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
|
+
):
|
|
82
84
|
value = validate_constraints(
|
|
83
85
|
value,
|
|
84
86
|
min_length=self.min_length,
|
|
@@ -182,20 +184,15 @@ class StaticaMeta(type):
|
|
|
182
184
|
|
|
183
185
|
# Generate custom __init__ method
|
|
184
186
|
|
|
185
|
-
def
|
|
187
|
+
def statica_init(self: Statica, **kwargs: Any) -> None:
|
|
186
188
|
for field_name, field_type in annotations.items():
|
|
187
|
-
|
|
188
|
-
if get_origin(field_type) is UnionType and type(None) in get_args(field_type):
|
|
189
|
-
if field_name not in kwargs:
|
|
190
|
-
setattr(self, field_name, None)
|
|
191
|
-
continue
|
|
192
|
-
|
|
193
|
-
if field_name not in kwargs:
|
|
189
|
+
if field_name not in kwargs and not isinstance(None, field_type):
|
|
194
190
|
msg = f"Missing required field: {field_name}"
|
|
195
191
|
raise TypeValidationError(msg)
|
|
196
|
-
setattr(self, field_name, kwargs[field_name])
|
|
197
192
|
|
|
198
|
-
|
|
193
|
+
setattr(self, field_name, kwargs.get(field_name))
|
|
194
|
+
|
|
195
|
+
namespace["__init__"] = statica_init
|
|
199
196
|
|
|
200
197
|
# Set up Field descriptors for type-hinted attributes
|
|
201
198
|
|
|
@@ -4,7 +4,6 @@ from types import UnionType
|
|
|
4
4
|
from typing import Any
|
|
5
5
|
|
|
6
6
|
from statica.exceptions import ConstraintValidationError, TypeValidationError
|
|
7
|
-
from statica.types_utils import value_matches_type
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
def validate_type(value: Any, expected_type: type | UnionType) -> None:
|
|
@@ -20,11 +19,12 @@ def validate_type(value: Any, expected_type: type | UnionType) -> None:
|
|
|
20
19
|
_validate_type(None, int | None) # No exception
|
|
21
20
|
_validate_type(None, int) # Raises TypeValidationError
|
|
22
21
|
"""
|
|
23
|
-
if
|
|
24
|
-
|
|
22
|
+
if isinstance(value, expected_type):
|
|
23
|
+
return
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
expected_type_str = str(expected_type) if type(expected_type) is UnionType else expected_type.__name__
|
|
26
|
+
msg = f"expected type '{expected_type_str}', got '{type(value).__name__}'"
|
|
27
|
+
raise TypeValidationError(msg)
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
def validate_constraints(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: statica
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2.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,7 +31,7 @@ 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.10
|
|
35
35
|
Description-Content-Type: text/markdown
|
|
36
36
|
|
|
37
37
|
# Statica
|
|
@@ -44,13 +44,13 @@ Statica is a Python library for defining and validating structured data with typ
|
|
|
44
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
|
+
Statica was created to address the need for a lightweight, flexible, and dependency-free alternative to libraries like pydantic.
|
|
48
48
|
While pydantic is a powerful tool for data validation and parsing, Statica offers some distinct advantages in specific situations:
|
|
49
49
|
|
|
50
50
|
1. **Lightweight**: Statica does not rely on any third-party dependencies, making it ideal for projects where minimizing external dependencies is a priority.
|
|
51
|
-
2. **
|
|
51
|
+
2. **Performance**: For use cases where performance is critical. Pydantic needs `3x` more memory than Statica for the same models.
|
|
52
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. **
|
|
53
|
+
4. **Customizable Validation**: Statica allows fine-grained control over type and constraint validation through customizable field descriptors (`Field`) and error classes.
|
|
54
54
|
|
|
55
55
|
## Features
|
|
56
56
|
|
|
@@ -4,7 +4,6 @@ pyproject.toml
|
|
|
4
4
|
statica/__init__.py
|
|
5
5
|
statica/core.py
|
|
6
6
|
statica/exceptions.py
|
|
7
|
-
statica/types_utils.py
|
|
8
7
|
statica/validators.py
|
|
9
8
|
statica.egg-info/PKG-INFO
|
|
10
9
|
statica.egg-info/SOURCES.txt
|
|
@@ -12,5 +11,4 @@ statica.egg-info/dependency_links.txt
|
|
|
12
11
|
statica.egg-info/top_level.txt
|
|
13
12
|
tests/test_aliasing.py
|
|
14
13
|
tests/test_statica.py
|
|
15
|
-
tests/test_types_utils.py
|
|
16
14
|
tests/test_validators.py
|
|
@@ -1,19 +1,6 @@
|
|
|
1
1
|
import pytest
|
|
2
2
|
|
|
3
|
-
from statica.core import ConstraintValidationError, Field, Statica, TypeValidationError
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_get_expected_type() -> None:
|
|
7
|
-
class TestClass(Statica):
|
|
8
|
-
required_field: str
|
|
9
|
-
optional_field: str | None
|
|
10
|
-
required_with_field: str = Field()
|
|
11
|
-
optional_with_field: str | None = Field()
|
|
12
|
-
|
|
13
|
-
assert get_expected_type(TestClass, "required_field") is str
|
|
14
|
-
assert get_expected_type(TestClass, "optional_field") == str | None
|
|
15
|
-
assert get_expected_type(TestClass, "required_with_field") is str
|
|
16
|
-
assert get_expected_type(TestClass, "optional_with_field") == str | None
|
|
3
|
+
from statica.core import ConstraintValidationError, Field, Statica, TypeValidationError
|
|
17
4
|
|
|
18
5
|
|
|
19
6
|
def test_basic_syntax() -> None:
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import Generic, TypeVar
|
|
2
|
-
|
|
3
1
|
import pytest
|
|
4
2
|
|
|
5
3
|
from statica.core import TypeValidationError
|
|
@@ -24,14 +22,3 @@ def test_validate_type() -> None:
|
|
|
24
22
|
validate_type(None, int)
|
|
25
23
|
with pytest.raises(TypeValidationError):
|
|
26
24
|
validate_type("abc", int | None)
|
|
27
|
-
|
|
28
|
-
# Test with generics
|
|
29
|
-
|
|
30
|
-
T = TypeVar("T")
|
|
31
|
-
|
|
32
|
-
class GenericTest(Generic[T]):
|
|
33
|
-
value: T
|
|
34
|
-
|
|
35
|
-
validate_type(GenericTest[int](), GenericTest[int])
|
|
36
|
-
|
|
37
|
-
validate_type(GenericTest[str](), GenericTest[str]) # Is this correct?
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from types import UnionType
|
|
4
|
-
from typing import Any, get_args, get_origin, get_type_hints
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def type_allows_none(expected_type: Any) -> bool:
|
|
8
|
-
"""
|
|
9
|
-
Check if the expected type allows None.
|
|
10
|
-
|
|
11
|
-
Examples:
|
|
12
|
-
.. code-block:: python
|
|
13
|
-
_allows_none(int | None) # True
|
|
14
|
-
_allows_none(int) # False
|
|
15
|
-
"""
|
|
16
|
-
if isinstance(expected_type, UnionType):
|
|
17
|
-
return type(None) in get_args(expected_type)
|
|
18
|
-
return expected_type is type(None) or expected_type is Any
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
def value_matches_type(value: Any | None, expected_type: Any) -> bool:
|
|
22
|
-
"""
|
|
23
|
-
Check if the value matches the expected type.
|
|
24
|
-
Handles basic types, Union types, and generic types.
|
|
25
|
-
|
|
26
|
-
Examples:
|
|
27
|
-
.. code-block:: python
|
|
28
|
-
_value_matches_type(1, int) # True
|
|
29
|
-
_value_matches_type(None, int | None) # True
|
|
30
|
-
_value_matches_type(None, int) # False
|
|
31
|
-
"""
|
|
32
|
-
# If expected_type is e.g. int | None, pass if value is None
|
|
33
|
-
if type_allows_none(expected_type) and value is None:
|
|
34
|
-
return True
|
|
35
|
-
|
|
36
|
-
# Basic types like int, str, etc.
|
|
37
|
-
if (origin := get_origin(expected_type)) is None:
|
|
38
|
-
return isinstance(value, expected_type)
|
|
39
|
-
|
|
40
|
-
# Handle Union types
|
|
41
|
-
if origin is UnionType or origin is type(None) or origin is Any:
|
|
42
|
-
types = get_args(expected_type)
|
|
43
|
-
return any(value_matches_type(value, t) for t in types if t is not type(None))
|
|
44
|
-
|
|
45
|
-
# Handle generic types
|
|
46
|
-
return isinstance(value, origin)
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def get_expected_type(cls: type, attr_name: str) -> Any:
|
|
50
|
-
"""
|
|
51
|
-
Get the expected type for a class attribute.
|
|
52
|
-
Handles type hints and Field descriptors.
|
|
53
|
-
|
|
54
|
-
Examples:
|
|
55
|
-
.. code-block:: python
|
|
56
|
-
class MyClass(Statica):
|
|
57
|
-
age: int | None
|
|
58
|
-
name: str = Field()
|
|
59
|
-
|
|
60
|
-
_get_expected_type(MyClass, "age") # int | None
|
|
61
|
-
_get_expected_type(MyClass, "name") # str
|
|
62
|
-
"""
|
|
63
|
-
|
|
64
|
-
return get_type_hints(cls).get(attr_name, Any)
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
from typing import Generic, TypeVar
|
|
2
|
-
|
|
3
|
-
from statica.types_utils import type_allows_none, value_matches_type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
def test_allows_none() -> None:
|
|
7
|
-
assert type_allows_none(type(None))
|
|
8
|
-
assert type_allows_none(int | None)
|
|
9
|
-
assert not type_allows_none(int)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def test_value_matches_type() -> None:
|
|
13
|
-
assert value_matches_type(1, int)
|
|
14
|
-
assert value_matches_type("abc", str)
|
|
15
|
-
assert not value_matches_type(1, str)
|
|
16
|
-
assert value_matches_type(None, int | None)
|
|
17
|
-
assert value_matches_type(1, int | None)
|
|
18
|
-
assert not value_matches_type(None, int)
|
|
19
|
-
assert value_matches_type(1, int | None)
|
|
20
|
-
assert not value_matches_type("abc", int | None)
|
|
21
|
-
|
|
22
|
-
# Test with generics
|
|
23
|
-
|
|
24
|
-
T = TypeVar("T")
|
|
25
|
-
|
|
26
|
-
class GenericTest(Generic[T]):
|
|
27
|
-
value: T
|
|
28
|
-
|
|
29
|
-
def __init__(self, value: T) -> None:
|
|
30
|
-
self.value = value
|
|
31
|
-
|
|
32
|
-
assert value_matches_type(GenericTest[int](1), GenericTest[int])
|
|
33
|
-
assert value_matches_type(GenericTest[int](1), GenericTest[str]) # Is this correct?
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|