Zod-inspired dict validation for Python. Zero deps. One file.
Project description
zodify
Zod-inspired dict validation for Python. Zero deps. One file.
Note: zodify is in alpha. The API is minimal and may change. Feedback and contributions are welcome!
Quick Start
from zodify import validate
config = validate(
{"port": int, "debug": bool},
{"port": 8080, "debug": True},
)
That's it. Plain dicts in, validated dicts out. No classes, no DSL, no dependencies.
Why zodify?
Most validation libraries ask you to learn a new DSL or model system. zodify doesn't.
| zodify | zon | zodic | Pydantic | |
|---|---|---|---|---|
| Philosophy | Minimalist | Full Zod port | Full Zod port | Full ORM |
| API style | Plain dicts | Chained builders | Chained builders | Classes |
| Dependencies | 0 | 2 | 0 | Many |
| Code size | <250 LOC | 1,000s+ LOC | 1,000s+ LOC | Large |
| Learning curve | Zero | Must learn DSL | Must learn DSL | Must learn DSL |
| Env var support | Built-in | No | No | Partial |
Install
pip install zodify
Requires Python 3.10+
To install from source:
git clone https://github.com/junyoung2015/zodify.git && pip install ./zodify
Usage
validate() — Dict Schema Validation
Define a schema as a plain dict of key: type pairs, then validate any dict against it.
from zodify import validate
schema = {"port": int, "debug": bool, "name": str}
# Exact type match
result = validate(schema, {"port": 8080, "debug": True, "name": "myapp"})
# → {"port": 8080, "debug": True, "name": "myapp"}
# Coerce strings — great for env vars and config files
raw = {"port": "8080", "debug": "true", "name": "myapp"}
result = validate(schema, raw, coerce=True)
# → {"port": 8080, "debug": True, "name": "myapp"}
# All errors are collected at once
validate({"a": int, "b": str}, {"a": "x", "b": 42})
# ValueError: a: expected int, got str
# b: expected str, got int
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
schema |
dict |
— | Mapping of keys to expected types (str, int, ...) |
data |
dict |
— | The dict to validate |
coerce |
bool |
False |
Cast string values to the target type when possible |
Behavior:
- Extra keys in
dataare silently stripped — only schema-declared keys are returned. - Missing keys raise
ValueError. - When
coerce=True, onlystrinputs are coerced (non-string mismatches still error). - Bool coercion accepts:
true/false,1/0,yes/no(case-insensitive).
Nested Dict Validation
Your schema can contain nested dicts — validation recurses automatically.
schema = {"db": {"host": str, "port": int}}
validate(schema, {"db": {"host": "localhost", "port": 5432}})
# → {"db": {"host": "localhost", "port": 5432}}
validate(schema, {"db": {"host": "localhost", "port": "bad"}})
# ValueError: db.port: expected int, got str
Errors use dot-notation paths: db.host, a.b.c, etc.
Optional Keys
Use Optional to mark keys that can be missing. Provide a default, or omit it to exclude the key from results.
from zodify import validate, Optional
schema = {
"host": str,
"port": Optional(int, 8080), # default 8080
"debug": Optional(bool), # absent if missing
}
validate(schema, {"host": "localhost"})
# → {"host": "localhost", "port": 8080}
Note:
Optionalshadowstyping.Optional. If you use both in the same file, alias it:from zodify import Optional as Optor usezodify.Optional(...).
List Element Validation
Use a single-element list as the schema value to validate every element in the list.
validate({"tags": [str]}, {"tags": ["python", "config"]})
# → {"tags": ["python", "config"]}
validate({"tags": [str]}, {"tags": ["ok", 42]})
# ValueError: tags[1]: expected str, got int
List of dicts works too:
validate(
{"users": [{"name": str, "age": int}]},
{"users": [{"name": "Alice", "age": 30}]},
)
Combined Example
All features compose naturally:
from zodify import validate, Optional
schema = {
"db": {"host": str, "port": Optional(int, 5432)},
"tags": [str],
"debug": Optional(bool, False),
}
validate(schema, {
"db": {"host": "localhost"},
"tags": ["prod"],
})
# → {"db": {"host": "localhost", "port": 5432},
# "tags": ["prod"], "debug": False}
env() — Typed Environment Variables
Read and type-cast environment variables with a single call.
from zodify import env
port = env("PORT", int, default=3000)
debug = env("DEBUG", bool, default=False)
secret = env("SECRET_KEY", str) # raises ValueError if missing
Parameters:
| Param | Type | Default | Description |
|---|---|---|---|
name |
str |
— | Environment variable name |
cast |
type |
— | Target type (str, int, float, bool) |
default |
any | (none) | Fallback if the var is unset. Not type-checked. |
Release Process
Release automation is tag-driven:
- Pushing a tag that matches
v*triggers.github/workflows/publish.yml. - The workflow runs tests, builds distributions, publishes to PyPI, and creates a GitHub Release.
- GitHub Release notes are sourced from the matching section in
CHANGELOG.md(for example,## [v0.1.0]).
Run local preflight before tagging:
./scripts/release_preflight.sh
If preflight passes, push the version tag:
git tag v0.1.0
git push origin v0.1.0
Roadmap
zodify is in alpha (v0.1.0). The API surface is small and may evolve.
Shipped in v0.1.0:
- Nested schema validation
- Optional keys with defaults
- List element validation
Near-term:
- Custom validator functions
- Chained builder API
Exploring:
-
.envfile loading - JSON Schema export
- Framework integrations (FastAPI, Django)
- Async validation support
License
MIT — 2026 Jun Young Sohn
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file zodify-0.1.0.tar.gz.
File metadata
- Download URL: zodify-0.1.0.tar.gz
- Upload date:
- Size: 12.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9e8572a1cd06079bf50c21a4ea136c29a1b034ec167beee5a1c78ac16806172e
|
|
| MD5 |
9080937a2afe29bd63ae6386d6b9a8dc
|
|
| BLAKE2b-256 |
56eee9299abd226744f4e6cbb6b2e24c01fac1d801f8cd4bf402bba8815ec7e5
|
Provenance
The following attestation bundles were made for zodify-0.1.0.tar.gz:
Publisher:
publish.yml on junyoung2015/zodify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zodify-0.1.0.tar.gz -
Subject digest:
9e8572a1cd06079bf50c21a4ea136c29a1b034ec167beee5a1c78ac16806172e - Sigstore transparency entry: 991751035
- Sigstore integration time:
-
Permalink:
junyoung2015/zodify@01479d8db17f7de96864b7638415826b73e0d45a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/junyoung2015
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@01479d8db17f7de96864b7638415826b73e0d45a -
Trigger Event:
push
-
Statement type:
File details
Details for the file zodify-0.1.0-py3-none-any.whl.
File metadata
- Download URL: zodify-0.1.0-py3-none-any.whl
- Upload date:
- Size: 7.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
adb12a32350c95d6bf2a3697013ebae5f70d87955b63ecbf6616ec50fa33b87a
|
|
| MD5 |
bcd46c11079eb981e8da70914563c03b
|
|
| BLAKE2b-256 |
0c1379dc321dd1f2cc4c44908bd03fcd7be66ecdda403bd5ec67424d76aac354
|
Provenance
The following attestation bundles were made for zodify-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on junyoung2015/zodify
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zodify-0.1.0-py3-none-any.whl -
Subject digest:
adb12a32350c95d6bf2a3697013ebae5f70d87955b63ecbf6616ec50fa33b87a - Sigstore transparency entry: 991751038
- Sigstore integration time:
-
Permalink:
junyoung2015/zodify@01479d8db17f7de96864b7638415826b73e0d45a -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/junyoung2015
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@01479d8db17f7de96864b7638415826b73e0d45a -
Trigger Event:
push
-
Statement type: