Skip to main content

Zod-inspired dict validation for Python. Zero deps. One file.

Project description

zodify

Zod-inspired dict validation for Python. Zero deps. One file.

PyPI version Python versions License


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 data are silently stripped — only schema-declared keys are returned.
  • Missing keys raise ValueError.
  • When coerce=True, only str inputs 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: Optional shadows typing.Optional. If you use both in the same file, alias it: from zodify import Optional as Opt or use zodify.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:

  • .env file loading
  • JSON Schema export
  • Framework integrations (FastAPI, Django)
  • Async validation support

License

MIT — 2026 Jun Young Sohn

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zodify-0.1.0.tar.gz (12.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zodify-0.1.0-py3-none-any.whl (7.0 kB view details)

Uploaded Python 3

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

Hashes for zodify-0.1.0.tar.gz
Algorithm Hash digest
SHA256 9e8572a1cd06079bf50c21a4ea136c29a1b034ec167beee5a1c78ac16806172e
MD5 9080937a2afe29bd63ae6386d6b9a8dc
BLAKE2b-256 56eee9299abd226744f4e6cbb6b2e24c01fac1d801f8cd4bf402bba8815ec7e5

See more details on using hashes here.

Provenance

The following attestation bundles were made for zodify-0.1.0.tar.gz:

Publisher: publish.yml on junyoung2015/zodify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for zodify-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 adb12a32350c95d6bf2a3697013ebae5f70d87955b63ecbf6616ec50fa33b87a
MD5 bcd46c11079eb981e8da70914563c03b
BLAKE2b-256 0c1379dc321dd1f2cc4c44908bd03fcd7be66ecdda403bd5ec67424d76aac354

See more details on using hashes here.

Provenance

The following attestation bundles were made for zodify-0.1.0-py3-none-any.whl:

Publisher: publish.yml on junyoung2015/zodify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page