validating 0.0.0__tar.gz → 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- validating-0.0.1/PKG-INFO +187 -0
- validating-0.0.1/README.md +171 -0
- {validating-0.0.0 → validating-0.0.1}/install.py +0 -7
- {validating-0.0.0 → validating-0.0.1}/pyproject.toml +5 -8
- validating-0.0.1/src/validating/__init__.py +179 -0
- validating-0.0.1/src/validating/core.py +13 -0
- validating-0.0.1/src/validating/datacls.py +176 -0
- validating-0.0.1/src/validating/valid_attr.py +716 -0
- validating-0.0.1/src/validating/valid_func.py +324 -0
- validating-0.0.1/test.py +669 -0
- validating-0.0.0/PKG-INFO +0 -42
- validating-0.0.0/README.md +0 -26
- validating-0.0.0/src/validating/__init__.py +0 -22
- validating-0.0.0/src/validating/__main__.py +0 -14
- validating-0.0.0/src/validating/_version.py +0 -3
- validating-0.0.0/src/validating/core.py +0 -9
- {validating-0.0.0 → validating-0.0.1}/.github/workflows/python-publish.yml +0 -0
- {validating-0.0.0 → validating-0.0.1}/.gitignore +0 -0
- {validating-0.0.0 → validating-0.0.1}/LICENSE +0 -0
- {validating-0.0.0 → validating-0.0.1}/src/validating/_typing.py +0 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: validating
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Provides a lightweight `attr()` descriptor factory that adds runtime validation to dataclass fields.
|
|
5
|
+
Project-URL: Documentation, https://github.com/Chitaoji/validating/blob/main/README.md
|
|
6
|
+
Project-URL: Repository, https://github.com/Chitaoji/validating/
|
|
7
|
+
Author-email: Chitaoji <2360742040@qq.com>
|
|
8
|
+
Maintainer-email: Chitaoji <2360742040@qq.com>
|
|
9
|
+
License-Expression: BSD-3-Clause
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: config
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Python: >=3.13
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
|
|
17
|
+
# validating
|
|
18
|
+
|
|
19
|
+
`validating` is a lightweight runtime validation library focused on making
|
|
20
|
+
`dataclass` fields and function arguments safer and easier to validate.
|
|
21
|
+
|
|
22
|
+
It exposes three main entry points:
|
|
23
|
+
|
|
24
|
+
- `attr(...)`: declare validated fields (type checks, bounds, allow/deny lists, custom validators)
|
|
25
|
+
- `@dataclass(...)`: a compatible enhancement of `dataclasses.dataclass` with automatic validation integration
|
|
26
|
+
- `@validate`: a decorator that validates function arguments using type annotations
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install validating
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from validating import attr, dataclass
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class UserConfig:
|
|
41
|
+
age: int = attr(lb=0)
|
|
42
|
+
role: str = attr(allowlist=["admin", "user"])
|
|
43
|
+
|
|
44
|
+
cfg = UserConfig(age=18, role="admin")
|
|
45
|
+
cfg.age = 20 # ✅
|
|
46
|
+
cfg.role = "user" # ✅
|
|
47
|
+
# cfg.age = -1 # ❌ ValueError
|
|
48
|
+
# cfg.role = "guest" # ❌ ValueError
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Core API
|
|
52
|
+
|
|
53
|
+
### `attr(...)`
|
|
54
|
+
|
|
55
|
+
Declares a descriptor-backed field that validates values on initialization and assignment.
|
|
56
|
+
|
|
57
|
+
Common parameters:
|
|
58
|
+
|
|
59
|
+
- `default`: default value
|
|
60
|
+
- `default_factory`: lazy default factory (mutually exclusive with `default`)
|
|
61
|
+
- `allowlist`: whitelist of allowed values
|
|
62
|
+
- `denylist`: blacklist of forbidden values
|
|
63
|
+
- `lb` / `ub`: inclusive lower / upper bounds
|
|
64
|
+
- `slb` / `sub`: exclusive lower / upper bounds
|
|
65
|
+
- `validator`: custom validator function with signature `Callable[[Any], bool]`
|
|
66
|
+
- `init` / `repr` / `hash` / `compare` / `kw_only`: forwarded dataclass field behavior controls
|
|
67
|
+
|
|
68
|
+
Error semantics:
|
|
69
|
+
|
|
70
|
+
- Misconfiguration at class-definition time (for example, default type mismatch) raises `ValidatorError`
|
|
71
|
+
- Invalid runtime values raise `TypeError` or `ValueError`
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
### `@dataclass(...)`
|
|
76
|
+
|
|
77
|
+
`validating.dataclass` is a compatible enhanced wrapper around `dataclasses.dataclass`.
|
|
78
|
+
|
|
79
|
+
Enhancements:
|
|
80
|
+
|
|
81
|
+
1. **Automatic default promotion**: `x: int = 1` is promoted to `attr(default=1)`
|
|
82
|
+
2. **Method argument validation**: when `validate_methods=True` (by default `False`), all public methods are wrapped with `validate` (including `@staticmethod` and `@classmethod`)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from validating import dataclass
|
|
86
|
+
|
|
87
|
+
@dataclass(validate_methods=True)
|
|
88
|
+
class Service:
|
|
89
|
+
retries: int = 3
|
|
90
|
+
|
|
91
|
+
def run(self, timeout: int) -> int:
|
|
92
|
+
return timeout + self.retries
|
|
93
|
+
|
|
94
|
+
svc = Service()
|
|
95
|
+
svc.run(1) # ✅
|
|
96
|
+
# svc.run("1") # ❌ TypeError
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
### `@validate`
|
|
102
|
+
|
|
103
|
+
Enables call-time argument validation based on type annotations.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from typing import Literal
|
|
107
|
+
from validating import validate
|
|
108
|
+
|
|
109
|
+
@validate
|
|
110
|
+
def configure(mode: Literal["dev", "prod"], workers: int):
|
|
111
|
+
return mode, workers
|
|
112
|
+
|
|
113
|
+
configure("dev", 4) # ✅
|
|
114
|
+
# configure("test", 4) # ❌ TypeError
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
`@validate` also handles `assert` statements in validated functions:
|
|
118
|
+
|
|
119
|
+
- `assert <expr>, "custom message"` keeps the original `AssertionError` with your message.
|
|
120
|
+
- `assert <expr>` (without message) is converted to a `ValueError` when `<expr>` is a direct assertion on function arguments, with a message like `expected <expr>, got <value> instead`.
|
|
121
|
+
- Assertions that are not direct checks on function arguments are left as regular `AssertionError`.
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from validating import validate
|
|
125
|
+
|
|
126
|
+
@validate
|
|
127
|
+
def check_score(score: int) -> int:
|
|
128
|
+
assert score >= 60
|
|
129
|
+
return score
|
|
130
|
+
|
|
131
|
+
check_score(80) # ✅
|
|
132
|
+
# check_score(59) # ❌ ValueError: expected score >= 60, got 59 instead
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## More Examples
|
|
136
|
+
|
|
137
|
+
### 1) `default_factory`
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from validating import attr, dataclass
|
|
141
|
+
|
|
142
|
+
@dataclass
|
|
143
|
+
class Cache:
|
|
144
|
+
items: list[int] = attr(default_factory=list)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2) Complex type hints
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from typing import Literal
|
|
151
|
+
from validating import attr, dataclass
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
class AppConfig:
|
|
155
|
+
mode: Literal["dev", "prod"] = attr()
|
|
156
|
+
token: int | str = attr()
|
|
157
|
+
mapping: dict[str, int] = attr()
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### 3) Custom validator
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from validating import attr, dataclass
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class EvenNumber:
|
|
167
|
+
value: int = attr(validator=lambda x: x % 2 == 0)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Notes
|
|
171
|
+
|
|
172
|
+
- Dataclasses with `slots=True` are currently not supported.
|
|
173
|
+
- This project focuses on runtime validation and does not replace static type checking.
|
|
174
|
+
|
|
175
|
+
## See Also
|
|
176
|
+
|
|
177
|
+
- GitHub: https://github.com/Chitaoji/validating/
|
|
178
|
+
- PyPI: https://pypi.org/project/validating/
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
This project falls under the BSD 3-Clause License.
|
|
182
|
+
|
|
183
|
+
## History
|
|
184
|
+
|
|
185
|
+
### v0.0.1
|
|
186
|
+
|
|
187
|
+
- Initial release.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# validating
|
|
2
|
+
|
|
3
|
+
`validating` is a lightweight runtime validation library focused on making
|
|
4
|
+
`dataclass` fields and function arguments safer and easier to validate.
|
|
5
|
+
|
|
6
|
+
It exposes three main entry points:
|
|
7
|
+
|
|
8
|
+
- `attr(...)`: declare validated fields (type checks, bounds, allow/deny lists, custom validators)
|
|
9
|
+
- `@dataclass(...)`: a compatible enhancement of `dataclasses.dataclass` with automatic validation integration
|
|
10
|
+
- `@validate`: a decorator that validates function arguments using type annotations
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install validating
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from validating import attr, dataclass
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class UserConfig:
|
|
25
|
+
age: int = attr(lb=0)
|
|
26
|
+
role: str = attr(allowlist=["admin", "user"])
|
|
27
|
+
|
|
28
|
+
cfg = UserConfig(age=18, role="admin")
|
|
29
|
+
cfg.age = 20 # ✅
|
|
30
|
+
cfg.role = "user" # ✅
|
|
31
|
+
# cfg.age = -1 # ❌ ValueError
|
|
32
|
+
# cfg.role = "guest" # ❌ ValueError
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Core API
|
|
36
|
+
|
|
37
|
+
### `attr(...)`
|
|
38
|
+
|
|
39
|
+
Declares a descriptor-backed field that validates values on initialization and assignment.
|
|
40
|
+
|
|
41
|
+
Common parameters:
|
|
42
|
+
|
|
43
|
+
- `default`: default value
|
|
44
|
+
- `default_factory`: lazy default factory (mutually exclusive with `default`)
|
|
45
|
+
- `allowlist`: whitelist of allowed values
|
|
46
|
+
- `denylist`: blacklist of forbidden values
|
|
47
|
+
- `lb` / `ub`: inclusive lower / upper bounds
|
|
48
|
+
- `slb` / `sub`: exclusive lower / upper bounds
|
|
49
|
+
- `validator`: custom validator function with signature `Callable[[Any], bool]`
|
|
50
|
+
- `init` / `repr` / `hash` / `compare` / `kw_only`: forwarded dataclass field behavior controls
|
|
51
|
+
|
|
52
|
+
Error semantics:
|
|
53
|
+
|
|
54
|
+
- Misconfiguration at class-definition time (for example, default type mismatch) raises `ValidatorError`
|
|
55
|
+
- Invalid runtime values raise `TypeError` or `ValueError`
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### `@dataclass(...)`
|
|
60
|
+
|
|
61
|
+
`validating.dataclass` is a compatible enhanced wrapper around `dataclasses.dataclass`.
|
|
62
|
+
|
|
63
|
+
Enhancements:
|
|
64
|
+
|
|
65
|
+
1. **Automatic default promotion**: `x: int = 1` is promoted to `attr(default=1)`
|
|
66
|
+
2. **Method argument validation**: when `validate_methods=True` (by default `False`), all public methods are wrapped with `validate` (including `@staticmethod` and `@classmethod`)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from validating import dataclass
|
|
70
|
+
|
|
71
|
+
@dataclass(validate_methods=True)
|
|
72
|
+
class Service:
|
|
73
|
+
retries: int = 3
|
|
74
|
+
|
|
75
|
+
def run(self, timeout: int) -> int:
|
|
76
|
+
return timeout + self.retries
|
|
77
|
+
|
|
78
|
+
svc = Service()
|
|
79
|
+
svc.run(1) # ✅
|
|
80
|
+
# svc.run("1") # ❌ TypeError
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### `@validate`
|
|
86
|
+
|
|
87
|
+
Enables call-time argument validation based on type annotations.
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from typing import Literal
|
|
91
|
+
from validating import validate
|
|
92
|
+
|
|
93
|
+
@validate
|
|
94
|
+
def configure(mode: Literal["dev", "prod"], workers: int):
|
|
95
|
+
return mode, workers
|
|
96
|
+
|
|
97
|
+
configure("dev", 4) # ✅
|
|
98
|
+
# configure("test", 4) # ❌ TypeError
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`@validate` also handles `assert` statements in validated functions:
|
|
102
|
+
|
|
103
|
+
- `assert <expr>, "custom message"` keeps the original `AssertionError` with your message.
|
|
104
|
+
- `assert <expr>` (without message) is converted to a `ValueError` when `<expr>` is a direct assertion on function arguments, with a message like `expected <expr>, got <value> instead`.
|
|
105
|
+
- Assertions that are not direct checks on function arguments are left as regular `AssertionError`.
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from validating import validate
|
|
109
|
+
|
|
110
|
+
@validate
|
|
111
|
+
def check_score(score: int) -> int:
|
|
112
|
+
assert score >= 60
|
|
113
|
+
return score
|
|
114
|
+
|
|
115
|
+
check_score(80) # ✅
|
|
116
|
+
# check_score(59) # ❌ ValueError: expected score >= 60, got 59 instead
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## More Examples
|
|
120
|
+
|
|
121
|
+
### 1) `default_factory`
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from validating import attr, dataclass
|
|
125
|
+
|
|
126
|
+
@dataclass
|
|
127
|
+
class Cache:
|
|
128
|
+
items: list[int] = attr(default_factory=list)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2) Complex type hints
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from typing import Literal
|
|
135
|
+
from validating import attr, dataclass
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class AppConfig:
|
|
139
|
+
mode: Literal["dev", "prod"] = attr()
|
|
140
|
+
token: int | str = attr()
|
|
141
|
+
mapping: dict[str, int] = attr()
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 3) Custom validator
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from validating import attr, dataclass
|
|
148
|
+
|
|
149
|
+
@dataclass
|
|
150
|
+
class EvenNumber:
|
|
151
|
+
value: int = attr(validator=lambda x: x % 2 == 0)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Notes
|
|
155
|
+
|
|
156
|
+
- Dataclasses with `slots=True` are currently not supported.
|
|
157
|
+
- This project focuses on runtime validation and does not replace static type checking.
|
|
158
|
+
|
|
159
|
+
## See Also
|
|
160
|
+
|
|
161
|
+
- GitHub: https://github.com/Chitaoji/validating/
|
|
162
|
+
- PyPI: https://pypi.org/project/validating/
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
This project falls under the BSD 3-Clause License.
|
|
166
|
+
|
|
167
|
+
## History
|
|
168
|
+
|
|
169
|
+
### v0.0.1
|
|
170
|
+
|
|
171
|
+
- Initial release.
|
|
@@ -21,7 +21,6 @@ HOMEPAGE: Final[str] = project["urls"]["Repository"]
|
|
|
21
21
|
REQUIRES: Final[list[str]] = project["dependencies"]
|
|
22
22
|
SOURCE = "src"
|
|
23
23
|
LICENSE = (here / project["license-files"][0]).read_text().partition("\n")[0]
|
|
24
|
-
VERSION = project["version"]
|
|
25
24
|
|
|
26
25
|
# Import the README and use it as the long-description.
|
|
27
26
|
readme_path = here / project["readme"]
|
|
@@ -84,10 +83,6 @@ def _quote(readme: str) -> str:
|
|
|
84
83
|
return f'"""{readme}"""'
|
|
85
84
|
|
|
86
85
|
|
|
87
|
-
def _version(version: str = VERSION) -> str:
|
|
88
|
-
return f'"""Version file."""\n\n__version__ = "{version}"\n'
|
|
89
|
-
|
|
90
|
-
|
|
91
86
|
class ReadmeFormatError(Exception):
|
|
92
87
|
"""Raised when the README has a wrong format."""
|
|
93
88
|
|
|
@@ -95,7 +90,6 @@ class ReadmeFormatError(Exception):
|
|
|
95
90
|
if __name__ == "__main__":
|
|
96
91
|
# Import the __init__.py and change the module docstring.
|
|
97
92
|
init_path = here / SOURCE / NAME / "__init__.py"
|
|
98
|
-
version_path = here / SOURCE / NAME / "_version.py"
|
|
99
93
|
module_file = init_path.read_text(encoding="utf-8")
|
|
100
94
|
new_doc, long_description = _readme2doc(long_description)
|
|
101
95
|
module_file = re.sub(
|
|
@@ -103,5 +97,4 @@ if __name__ == "__main__":
|
|
|
103
97
|
)
|
|
104
98
|
init_path.write_text(module_file, encoding="utf-8")
|
|
105
99
|
readme_path.write_text(long_description.strip(), encoding="utf-8")
|
|
106
|
-
version_path.write_text(_version())
|
|
107
100
|
os.system(f"cd {here} && python -m build")
|
|
@@ -4,28 +4,25 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "validating"
|
|
7
|
-
version = "0.0.
|
|
7
|
+
version = "0.0.1"
|
|
8
8
|
dependencies = []
|
|
9
|
-
requires-python = ">=3.
|
|
9
|
+
requires-python = ">=3.13"
|
|
10
10
|
authors = [
|
|
11
11
|
{name = "Chitaoji", email = "2360742040@qq.com"},
|
|
12
12
|
]
|
|
13
13
|
maintainers = [
|
|
14
14
|
{name = "Chitaoji", email = "2360742040@qq.com"}
|
|
15
15
|
]
|
|
16
|
-
description = "
|
|
16
|
+
description = "Provides a lightweight `attr()` descriptor factory that adds runtime validation to dataclass fields."
|
|
17
17
|
readme = "README.md"
|
|
18
18
|
license = "BSD-3-Clause"
|
|
19
19
|
license-files = ["LICENSE"]
|
|
20
20
|
keywords = ["config"]
|
|
21
21
|
classifiers = [
|
|
22
22
|
"Programming Language :: Python :: 3",
|
|
23
|
-
"Programming Language :: Python :: 3.
|
|
23
|
+
"Programming Language :: Python :: 3.13"
|
|
24
24
|
]
|
|
25
25
|
|
|
26
26
|
[project.urls]
|
|
27
27
|
Documentation = "https://github.com/Chitaoji/validating/blob/main/README.md"
|
|
28
|
-
Repository = "https://github.com/Chitaoji/validating/"
|
|
29
|
-
|
|
30
|
-
[project.scripts]
|
|
31
|
-
cfg = "template.__main__:run"
|
|
28
|
+
Repository = "https://github.com/Chitaoji/validating/"
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""
|
|
2
|
+
# validating
|
|
3
|
+
|
|
4
|
+
`validating` is a lightweight runtime validation library focused on making
|
|
5
|
+
`dataclass` fields and function arguments safer and easier to validate.
|
|
6
|
+
|
|
7
|
+
It exposes three main entry points:
|
|
8
|
+
|
|
9
|
+
- `attr(...)`: declare validated fields (type checks, bounds, allow/deny lists, custom
|
|
10
|
+
validators)
|
|
11
|
+
- `@dataclass(...)`: a compatible enhancement of `dataclasses.dataclass` with automatic
|
|
12
|
+
validation integration
|
|
13
|
+
- `@validate`: a decorator that validates function arguments using type annotations
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from validating import attr, dataclass
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class UserConfig:
|
|
22
|
+
age: int = attr(lb=0)
|
|
23
|
+
role: str = attr(allowlist=["admin", "user"])
|
|
24
|
+
|
|
25
|
+
cfg = UserConfig(age=18, role="admin")
|
|
26
|
+
cfg.age = 20 # ✅
|
|
27
|
+
cfg.role = "user" # ✅
|
|
28
|
+
# cfg.age = -1 # ❌ ValueError
|
|
29
|
+
# cfg.role = "guest" # ❌ ValueError
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Core API
|
|
33
|
+
|
|
34
|
+
### `attr(...)`
|
|
35
|
+
|
|
36
|
+
Declares a descriptor-backed field that validates values on initialization and
|
|
37
|
+
assignment.
|
|
38
|
+
|
|
39
|
+
Common parameters:
|
|
40
|
+
|
|
41
|
+
- `default`: default value
|
|
42
|
+
- `default_factory`: lazy default factory (mutually exclusive with `default`)
|
|
43
|
+
- `allowlist`: whitelist of allowed values
|
|
44
|
+
- `denylist`: blacklist of forbidden values
|
|
45
|
+
- `lb` / `ub`: inclusive lower / upper bounds
|
|
46
|
+
- `slb` / `sub`: exclusive lower / upper bounds
|
|
47
|
+
- `validator`: custom validator function with signature `Callable[[Any], bool]`
|
|
48
|
+
- `init` / `repr` / `hash` / `compare` / `kw_only`: forwarded dataclass field behavior
|
|
49
|
+
controls
|
|
50
|
+
|
|
51
|
+
Error semantics:
|
|
52
|
+
|
|
53
|
+
- Misconfiguration at class-definition time (for example, default type mismatch) raises
|
|
54
|
+
`ValidatorError`
|
|
55
|
+
- Invalid runtime values raise `TypeError` or `ValueError`
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
### `@dataclass(...)`
|
|
60
|
+
|
|
61
|
+
`validating.dataclass` is a compatible enhanced wrapper around `dataclasses.dataclass`.
|
|
62
|
+
|
|
63
|
+
Enhancements:
|
|
64
|
+
|
|
65
|
+
1. **Automatic default promotion**: `x: int = 1` is promoted to `attr(default=1)`
|
|
66
|
+
2. **Method argument validation**: when `validate_methods=True` (by default `False`),
|
|
67
|
+
all public methods are wrapped with `validate` (including `@staticmethod` and
|
|
68
|
+
`@classmethod`)
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
from validating import dataclass
|
|
72
|
+
|
|
73
|
+
@dataclass(validate_methods=True)
|
|
74
|
+
class Service:
|
|
75
|
+
retries: int = 3
|
|
76
|
+
|
|
77
|
+
def run(self, timeout: int) -> int:
|
|
78
|
+
return timeout + self.retries
|
|
79
|
+
|
|
80
|
+
svc = Service()
|
|
81
|
+
svc.run(1) # ✅
|
|
82
|
+
# svc.run("1") # ❌ TypeError
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### `@validate`
|
|
88
|
+
|
|
89
|
+
Enables call-time argument validation based on type annotations.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from typing import Literal
|
|
93
|
+
from validating import validate
|
|
94
|
+
|
|
95
|
+
@validate
|
|
96
|
+
def configure(mode: Literal["dev", "prod"], workers: int):
|
|
97
|
+
return mode, workers
|
|
98
|
+
|
|
99
|
+
configure("dev", 4) # ✅
|
|
100
|
+
# configure("test", 4) # ❌ TypeError
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`@validate` also handles `assert` statements in validated functions:
|
|
104
|
+
|
|
105
|
+
- `assert <expr>, "custom message"` keeps the original `AssertionError` with your
|
|
106
|
+
message.
|
|
107
|
+
- `assert <expr>` (without message) is converted to a `ValueError` when `<expr>` is a
|
|
108
|
+
direct assertion on function arguments, with a message like `expected <expr>, got
|
|
109
|
+
<value> instead`.
|
|
110
|
+
- Assertions that are not direct checks on function arguments are left as regular
|
|
111
|
+
`AssertionError`.
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from validating import validate
|
|
115
|
+
|
|
116
|
+
@validate
|
|
117
|
+
def check_score(score: int) -> int:
|
|
118
|
+
assert score >= 60
|
|
119
|
+
return score
|
|
120
|
+
|
|
121
|
+
check_score(80) # ✅
|
|
122
|
+
# check_score(59) # ❌ ValueError: expected score >= 60, got 59 instead
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## More Examples
|
|
126
|
+
|
|
127
|
+
### 1) `default_factory`
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from validating import attr, dataclass
|
|
131
|
+
|
|
132
|
+
@dataclass
|
|
133
|
+
class Cache:
|
|
134
|
+
items: list[int] = attr(default_factory=list)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 2) Complex type hints
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
from typing import Literal
|
|
141
|
+
from validating import attr, dataclass
|
|
142
|
+
|
|
143
|
+
@dataclass
|
|
144
|
+
class AppConfig:
|
|
145
|
+
mode: Literal["dev", "prod"] = attr()
|
|
146
|
+
token: int | str = attr()
|
|
147
|
+
mapping: dict[str, int] = attr()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 3) Custom validator
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from validating import attr, dataclass
|
|
154
|
+
|
|
155
|
+
@dataclass
|
|
156
|
+
class EvenNumber:
|
|
157
|
+
value: int = attr(validator=lambda x: x % 2 == 0)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Notes
|
|
161
|
+
|
|
162
|
+
- Dataclasses with `slots=True` are currently not supported.
|
|
163
|
+
- This project focuses on runtime validation and does not replace static type checking.
|
|
164
|
+
|
|
165
|
+
## See Also
|
|
166
|
+
|
|
167
|
+
- GitHub: https://github.com/Chitaoji/validating/
|
|
168
|
+
- PyPI: https://pypi.org/project/validating/
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
This project falls under the BSD 3-Clause License.
|
|
172
|
+
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
from . import core
|
|
176
|
+
from .core import *
|
|
177
|
+
|
|
178
|
+
__all__: list[str] = []
|
|
179
|
+
__all__.extend(core.__all__)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Contains the core of validating: attr(), dataclass(), validate(), etc.
|
|
3
|
+
|
|
4
|
+
NOTE: this module is private. All functions and objects are available in the main
|
|
5
|
+
:mod:`validating` namespace - use that instead.
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .datacls import dataclass
|
|
10
|
+
from .valid_attr import ValidatorError, attr
|
|
11
|
+
from .valid_func import validate
|
|
12
|
+
|
|
13
|
+
__all__ = ["attr", "dataclass", "validate", "ValidatorError"]
|