guardly 0.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.
- guardly-0.2.0/.gitignore +24 -0
- guardly-0.2.0/CHANGELOG.md +26 -0
- guardly-0.2.0/CONTRIBUTING.md +66 -0
- guardly-0.2.0/PKG-INFO +392 -0
- guardly-0.2.0/README.md +378 -0
- guardly-0.2.0/pyproject.toml +23 -0
- guardly-0.2.0/src/guardly/__init__.py +21 -0
- guardly-0.2.0/src/guardly/errors.py +28 -0
- guardly-0.2.0/src/guardly/types.py +266 -0
- guardly-0.2.0/src/guardly/validator.py +16 -0
- guardly-0.2.0/tests/test_guard.py +578 -0
guardly-0.2.0/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
*.pyc
|
|
2
|
+
__pycache__/
|
|
3
|
+
.venv/
|
|
4
|
+
*.py[cod]
|
|
5
|
+
.env
|
|
6
|
+
node_modules/
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
.DS_Store
|
|
11
|
+
whenly/
|
|
12
|
+
unbreak/
|
|
13
|
+
structmatch/
|
|
14
|
+
configly/
|
|
15
|
+
typely/
|
|
16
|
+
batchly/
|
|
17
|
+
trending-bloom/
|
|
18
|
+
openclaw_workspace/
|
|
19
|
+
|
|
20
|
+
retryly/
|
|
21
|
+
workspace-researcher/
|
|
22
|
+
git/
|
|
23
|
+
.openclaw/
|
|
24
|
+
trendsketch/
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Guardly will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-03-24
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- Initial release of Guardly — schema-first validation for Python dicts
|
|
12
|
+
- Bare type validators: `str`, `int`, `float`, `bool`
|
|
13
|
+
- Constrained type validators: `Int()`, `Str()`, `Float()`, `Bool()`
|
|
14
|
+
- Specialized validators: `Email()`, `OneOf()`
|
|
15
|
+
- Collection validators: `List()`, `Dict()`
|
|
16
|
+
- `Optional()` wrapper for optional fields with default support
|
|
17
|
+
- Custom validator support (any callable as a schema node)
|
|
18
|
+
- Nested object validation (schemas within schemas)
|
|
19
|
+
- List shorthand syntax: `[str]`, `[int]`
|
|
20
|
+
- Type coercion: `str→int`, `str→float`, `str→bool`
|
|
21
|
+
- `validate()` — returns list of errors
|
|
22
|
+
- `check()` — raises `ValidationError`
|
|
23
|
+
- `ValidationIssue` with path and message
|
|
24
|
+
- `ValidationError` exception with all errors
|
|
25
|
+
- Zero external dependencies, pure Python 3.10+
|
|
26
|
+
- 106 tests with full coverage
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Contributing to Guardly
|
|
2
|
+
|
|
3
|
+
Thank you for your interest in contributing! Guardly is a lightweight library and contributions of all sizes are welcome.
|
|
4
|
+
|
|
5
|
+
## Setup
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Clone the repository
|
|
9
|
+
git clone https://github.com/yourusername/guardly.git
|
|
10
|
+
cd guardly
|
|
11
|
+
|
|
12
|
+
# Create a virtual environment (recommended)
|
|
13
|
+
python -m venv .venv
|
|
14
|
+
source .venv/bin/activate # or .venv\Scripts\activate on Windows
|
|
15
|
+
|
|
16
|
+
# Install in editable mode with dev dependencies
|
|
17
|
+
pip install -e ".[dev]"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running Tests
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Run all tests
|
|
24
|
+
python -m pytest tests/ -v
|
|
25
|
+
|
|
26
|
+
# Run with coverage
|
|
27
|
+
python -m pytest tests/ -v --cov=guardly
|
|
28
|
+
|
|
29
|
+
# Run a specific test class
|
|
30
|
+
python -m pytest tests/test_guard.py::TestBasicStr -v
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Development Philosophy
|
|
34
|
+
|
|
35
|
+
Guardly has a few core principles that guide contributions:
|
|
36
|
+
|
|
37
|
+
1. **Zero dependencies.** PRs that add external dependencies will not be accepted. If functionality requires a dependency, it belongs in an optional extra (`guardly[email]`, etc.).
|
|
38
|
+
|
|
39
|
+
2. **Schema-as-data.** The schema is a dict, not a class. API design should reinforce this.
|
|
40
|
+
|
|
41
|
+
3. **Clear error messages.** Every error should tell the user what went wrong, where it went wrong, and what was expected.
|
|
42
|
+
|
|
43
|
+
4. **Coercion is opt-in.** Bare types (`str`, `int`) are strict. Use the validator constructors (`Str()`, `Int()`) for coercion behavior.
|
|
44
|
+
|
|
45
|
+
## Pull Request Process
|
|
46
|
+
|
|
47
|
+
1. Fork the repository
|
|
48
|
+
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
|
49
|
+
3. Write tests for your changes
|
|
50
|
+
4. Ensure all tests pass (`python -m pytest tests/ -v`)
|
|
51
|
+
5. Commit with clear messages
|
|
52
|
+
6. Open a pull request
|
|
53
|
+
|
|
54
|
+
## Reporting Bugs
|
|
55
|
+
|
|
56
|
+
Please open a GitHub issue with:
|
|
57
|
+
- A minimal reproducible example
|
|
58
|
+
- The expected vs actual behavior
|
|
59
|
+
- Python version and OS
|
|
60
|
+
|
|
61
|
+
## Code Style
|
|
62
|
+
|
|
63
|
+
- Follow PEP 8
|
|
64
|
+
- Type hints where helpful
|
|
65
|
+
- Docstrings on public functions/classes
|
|
66
|
+
- Keep it simple — this is a lightweight library
|
guardly-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: guardly
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Schema-first validation for dicts and configs. Zero dependencies.
|
|
5
|
+
Author: Ravi Teja Prabhala Venkata
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: config,dict,schema,validation
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# Guardly
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+

|
|
19
|
+

|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
**Schema-first validation for Python dicts and configs. Zero dependencies.**
|
|
23
|
+
|
|
24
|
+
Guardly validates plain dictionaries against schemas you define as data — no models, no decorators, no ceremony. Define what you expect, get back clear errors with path info.
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
import guardly
|
|
28
|
+
|
|
29
|
+
schema = {
|
|
30
|
+
"name": str,
|
|
31
|
+
"age": guardly.Int(min=0, max=150),
|
|
32
|
+
"email": guardly.Email(),
|
|
33
|
+
"tags": [str],
|
|
34
|
+
"active": bool,
|
|
35
|
+
"score": float,
|
|
36
|
+
"address": {
|
|
37
|
+
"city": str,
|
|
38
|
+
"zip": guardly.Str(pattern=r"\d{5}"),
|
|
39
|
+
},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
errors = guardly.validate(data, schema)
|
|
43
|
+
if errors:
|
|
44
|
+
for e in errors:
|
|
45
|
+
print(f"{'.'.join(e.path)}: {e.message}")
|
|
46
|
+
|
|
47
|
+
# Or raise on failure:
|
|
48
|
+
guardly.check(data, schema) # raises guardly.ValidationError
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Why Guardly?
|
|
54
|
+
|
|
55
|
+
Python's validation ecosystem has a gap. Here's the landscape:
|
|
56
|
+
|
|
57
|
+
| Library | Approach | Dependencies | Status |
|
|
58
|
+
|---------|----------|-------------|--------|
|
|
59
|
+
| **Pydantic** | Model-first (class-based) | pydantic-core, typing-extensions | Active, heavy |
|
|
60
|
+
| **Marshmallow** | Schema-based (class declarations) | marshmallow, packaging | Active, verbose |
|
|
61
|
+
| **Cerberus** | Schema-as-dict | None | Last release 2021, unmaintained |
|
|
62
|
+
| **Voluptuous** | Schema-as-dict | None | Last release 2020, unmaintained |
|
|
63
|
+
| **jsonschema** | JSON Schema spec | jsonschema, attrs, rpds-py | Active, spec-heavy |
|
|
64
|
+
| **Guardly** | Schema-as-dict | **None** | ✅ Active, lightweight |
|
|
65
|
+
|
|
66
|
+
**The problem:** If you're validating raw dicts — API payloads, config files, environment variables — you don't need models. Pydantic forces a class-based design that's overkill for simple validation. Cerberus and Voluptuous filled this niche but are now unmaintained. Marshmallow requires declaring classes.
|
|
67
|
+
|
|
68
|
+
**Guardly fills this gap:** Define schemas as plain Python dicts. Validate any dict against them. Get clear, path-annotated errors. Zero dependencies. 100% pure Python.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Installation
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install guardly
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
No other dependencies. Python 3.10+.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick Start
|
|
83
|
+
|
|
84
|
+
### Basic Types
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
import guardly
|
|
88
|
+
|
|
89
|
+
schema = {
|
|
90
|
+
"name": str,
|
|
91
|
+
"age": int,
|
|
92
|
+
"score": float,
|
|
93
|
+
"active": bool,
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
errors = guardly.validate({"name": "Alice", "age": 30, "score": 95.5, "active": True}, schema)
|
|
97
|
+
# errors == []
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Constrained Types
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
schema = {
|
|
104
|
+
"age": guardly.Int(min=0, max=150),
|
|
105
|
+
"email": guardly.Str(pattern=r"[^@]+@[^@]+\.[^@]+"),
|
|
106
|
+
"bio": guardly.Str(min_len=10, max_len=500),
|
|
107
|
+
"rating": guardly.Float(min=0.0, max=5.0),
|
|
108
|
+
"role": guardly.OneOf(["admin", "editor", "viewer"]),
|
|
109
|
+
"contact": guardly.Email(),
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Nested Schemas
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
schema = {
|
|
117
|
+
"user": {
|
|
118
|
+
"name": str,
|
|
119
|
+
"address": {
|
|
120
|
+
"street": str,
|
|
121
|
+
"city": str,
|
|
122
|
+
"zip": guardly.Str(pattern=r"\d{5}"),
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Lists
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
# Shorthand: all elements must be strings
|
|
132
|
+
schema = {"tags": [str]}
|
|
133
|
+
|
|
134
|
+
# Full control with List()
|
|
135
|
+
schema = {"scores": guardly.List(guardly.Float(min=0, max=100), min_len=1, max_len=10)}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Dict (untyped keys, typed values)
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
schema = {"metadata": guardly.Dict(int)}
|
|
142
|
+
# validates {"metadata": {"views": 100, "likes": 42}}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Optional Fields
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
schema = {
|
|
149
|
+
"name": str,
|
|
150
|
+
"nickname": guardly.Optional(str), # can be missing or None
|
|
151
|
+
"role": guardly.Optional(guardly.Str(), default="viewer"),
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Custom Validators
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
schema = {
|
|
159
|
+
"password": lambda x: len(x) >= 8 or "password must be at least 8 characters",
|
|
160
|
+
"age": lambda x: x >= 0 or "age must be non-negative",
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Error Handling
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# Collect all errors:
|
|
168
|
+
errors = guardly.validate(data, schema)
|
|
169
|
+
for e in errors:
|
|
170
|
+
print(f"[{'.'.join(e.path) if e.path else 'root'}] {e.message}")
|
|
171
|
+
|
|
172
|
+
# Or raise immediately:
|
|
173
|
+
try:
|
|
174
|
+
guardly.check(data, schema)
|
|
175
|
+
except guardly.ValidationError as e:
|
|
176
|
+
for e in e.errors:
|
|
177
|
+
print(e)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Type System
|
|
183
|
+
|
|
184
|
+
### Primitive Types
|
|
185
|
+
|
|
186
|
+
| Schema | Validates | Coercions |
|
|
187
|
+
|--------|-----------|-----------|
|
|
188
|
+
| `str` | strings only | — |
|
|
189
|
+
| `int` | integers | `"42"` → `42` |
|
|
190
|
+
| `float` | floats | `42` → `42.0`, `"3.14"` → `3.14` |
|
|
191
|
+
| `bool` | booleans | `"true"`/`"false"`/`"yes"`/`"no"`/`0`/`1` |
|
|
192
|
+
|
|
193
|
+
### Constrained Types
|
|
194
|
+
|
|
195
|
+
| Type | Parameters | Example |
|
|
196
|
+
|------|-----------|---------|
|
|
197
|
+
| `Int(min, max)` | min/max bounds | `Int(min=0, max=150)` |
|
|
198
|
+
| `Str(pattern, min_len, max_len)` | regex, length bounds | `Str(pattern=r"\d{5}")` |
|
|
199
|
+
| `Float(min, max)` | min/max bounds | `Float(min=0.0, max=1.0)` |
|
|
200
|
+
| `Email()` | built-in regex | `Email()` |
|
|
201
|
+
| `OneOf(choices)` | allowed values | `OneOf(["a", "b", "c"])` |
|
|
202
|
+
| `List(element_type, min_len, max_len)` | element type, size bounds | `List(int, min_len=1)` |
|
|
203
|
+
| `Dict(value_type)` | value type (keys unrestricted) | `Dict(int)` |
|
|
204
|
+
| `Optional(type, default)` | wraps any type as optional | `Optional(str, default="N/A")` |
|
|
205
|
+
|
|
206
|
+
### Custom Validators
|
|
207
|
+
|
|
208
|
+
Any callable works as a schema node:
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# Return truthy for pass, falsy for fail
|
|
212
|
+
schema = {"x": lambda x: x > 0}
|
|
213
|
+
|
|
214
|
+
# Return error message string on failure
|
|
215
|
+
schema = {"x": lambda x: x > 0 or "must be positive"}
|
|
216
|
+
|
|
217
|
+
# Raise an exception
|
|
218
|
+
schema = {"x": lambda x: assert x > 0}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## Design Philosophy
|
|
224
|
+
|
|
225
|
+
1. **Schema-as-data.** Your schema is a Python dict, not a class. It's serializable, composable, and trivially dynamic. You can load it from JSON, generate it programmatically, or compose it from pieces.
|
|
226
|
+
|
|
227
|
+
2. **Zero dependencies.** Guardly is ~250 lines of pure Python. No pydantic-core, no attrs, no typing extensions. Install it, use it, ship it — nothing else to track.
|
|
228
|
+
|
|
229
|
+
3. **Clear errors with paths.** Every validation error tells you exactly where it is (`address.zip`), what went wrong, and what was expected. No hunting through nested exceptions.
|
|
230
|
+
|
|
231
|
+
4. **Coercion where sensible, strict where it matters.** `"42"` coerces to `42` for `Int()` because that's what most APIs need. But `True` never coerces to `1` — that's a bug waiting to happen.
|
|
232
|
+
|
|
233
|
+
5. **Extra fields ignored by default.** Your schema declares what you need. Additional keys in the data are silently ignored. This makes forward-compatible APIs natural.
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Use Cases
|
|
238
|
+
|
|
239
|
+
### API Input Validation
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
def create_user(request_json):
|
|
243
|
+
schema = {
|
|
244
|
+
"username": Str(min_len=3, max_len=32, pattern=r"[a-zA-Z0-9_]+"),
|
|
245
|
+
"email": Email(),
|
|
246
|
+
"age": Optional(Int(min=13, max=120)),
|
|
247
|
+
"role": Optional(OneOf(["user", "admin"]), default="user"),
|
|
248
|
+
}
|
|
249
|
+
errors = guardly.validate(request_json, schema)
|
|
250
|
+
if errors:
|
|
251
|
+
return {"errors": [{"field": ".".join(e.path), "msg": e.message} for e in errors]}, 400
|
|
252
|
+
# proceed with validated data...
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Config File Validation
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
import json
|
|
259
|
+
|
|
260
|
+
CONFIG_SCHEMA = {
|
|
261
|
+
"database": {
|
|
262
|
+
"host": str,
|
|
263
|
+
"port": Int(min=1, max=65535),
|
|
264
|
+
"name": str,
|
|
265
|
+
"pool_size": Optional(Int(min=1, max=100), default=10),
|
|
266
|
+
},
|
|
267
|
+
"server": {
|
|
268
|
+
"host": str,
|
|
269
|
+
"port": Int(min=1, max=65535),
|
|
270
|
+
"debug": bool,
|
|
271
|
+
},
|
|
272
|
+
"logging": {
|
|
273
|
+
"level": OneOf(["DEBUG", "INFO", "WARNING", "ERROR"]),
|
|
274
|
+
"file": Optional(str),
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
with open("config.json") as f:
|
|
279
|
+
config = json.load(f)
|
|
280
|
+
guardly.check(config, CONFIG_SCHEMA)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Environment Variable Validation
|
|
284
|
+
|
|
285
|
+
```python
|
|
286
|
+
import os
|
|
287
|
+
|
|
288
|
+
env_schema = {
|
|
289
|
+
"DATABASE_URL": Str(pattern=r"postgres://.+"),
|
|
290
|
+
"PORT": Int(min=1, max=65535),
|
|
291
|
+
"DEBUG": bool,
|
|
292
|
+
"SECRET_KEY": Str(min_len=32),
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
env_data = {k: os.environ.get(k) for k in env_schema}
|
|
296
|
+
errors = guardly.validate(env_data, env_schema)
|
|
297
|
+
if errors:
|
|
298
|
+
raise RuntimeError(f"Invalid environment: {errors}")
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Form Data Validation
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
form_schema = {
|
|
305
|
+
"email": Email(),
|
|
306
|
+
"password": Str(min_len=8),
|
|
307
|
+
"confirm_password": str,
|
|
308
|
+
"terms_accepted": bool,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
# Custom cross-field validation
|
|
312
|
+
errors = guardly.validate(form_data, form_schema)
|
|
313
|
+
if form_data.get("password") != form_data.get("confirm_password"):
|
|
314
|
+
errors.append(guardly.errors.ValidationIssue(
|
|
315
|
+
("confirm_password",), "passwords do not match"
|
|
316
|
+
))
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## API Reference
|
|
322
|
+
|
|
323
|
+
### `guardly.validate(data, schema) -> list[ValidationIssue]`
|
|
324
|
+
|
|
325
|
+
Validates `data` (a dict) against `schema`. Returns a list of errors. Empty list means valid.
|
|
326
|
+
|
|
327
|
+
### `guardly.check(data, schema)`
|
|
328
|
+
|
|
329
|
+
Validates and raises `ValidationError` if any errors are found.
|
|
330
|
+
|
|
331
|
+
### `guardly.ValidationError`
|
|
332
|
+
|
|
333
|
+
Exception raised by `check()`. Contains `.errors` — a list of `ValidationIssue` objects.
|
|
334
|
+
|
|
335
|
+
### `guardly.errors.ValidationIssue`
|
|
336
|
+
|
|
337
|
+
- `.path` — tuple of path segments, e.g., `("address", "zip")`
|
|
338
|
+
- `.message` — human-readable error description
|
|
339
|
+
|
|
340
|
+
### Types
|
|
341
|
+
|
|
342
|
+
- `guardly.Int(min=None, max=None)`
|
|
343
|
+
- `guardly.Str(pattern=None, min_len=None, max_len=None)`
|
|
344
|
+
- `guardly.Float(min=None, max=None)`
|
|
345
|
+
- `guardly.Bool()`
|
|
346
|
+
- `guardly.Email()`
|
|
347
|
+
- `guardly.OneOf(choices)`
|
|
348
|
+
- `guardly.List(element_type, min_len=None, max_len=None)`
|
|
349
|
+
- `guardly.Dict(value_type)`
|
|
350
|
+
- `guardly.Optional(type, default=None)`
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Comparison: Guardly vs Alternatives
|
|
355
|
+
|
|
356
|
+
| Feature | Guardly | Pydantic | Cerberus | Voluptuous | Marshmallow | jsonschema |
|
|
357
|
+
|---------|---------|----------|----------|------------|-------------|------------|
|
|
358
|
+
| Schema-as-dict | ✅ | ❌ (class-based) | ✅ | ✅ | ❌ (class-based) | ✅ (JSON Schema) |
|
|
359
|
+
| Zero dependencies | ✅ | ❌ | ✅ | ✅ | ❌ | ❌ |
|
|
360
|
+
| Type coercion | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
|
|
361
|
+
| Nested validation | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
362
|
+
| Custom validators | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
363
|
+
| Error path info | ✅ | ✅ | ✅ | Partial | ✅ | ✅ |
|
|
364
|
+
| Optional fields | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
|
|
365
|
+
| Maintained (2024+) | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
|
|
366
|
+
| Lines of code | ~250 | ~20,000 | ~5,000 | ~2,500 | ~10,000 | ~8,000 |
|
|
367
|
+
| Install size | ~10 KB | ~5 MB | ~200 KB | ~150 KB | ~500 KB | ~1 MB |
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Development
|
|
372
|
+
|
|
373
|
+
```bash
|
|
374
|
+
# Clone and set up
|
|
375
|
+
git clone https://github.com/yourusername/guardly.git
|
|
376
|
+
cd guardly
|
|
377
|
+
pip install -e ".[dev]"
|
|
378
|
+
|
|
379
|
+
# Run tests
|
|
380
|
+
python -m pytest tests/ -v
|
|
381
|
+
|
|
382
|
+
# Run with coverage
|
|
383
|
+
python -m pytest tests/ -v --cov=guardly
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
387
|
+
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## License
|
|
391
|
+
|
|
392
|
+
MIT © Ravi Teja Prabhala Venkata
|