Skip to main content

🌅 Kankyō

Define your application's environment variables in a Python schema, then generate a typed configuration client.

Define, generate, load

Install into your application's virtual environment:

python -m pip install kankyo

Create env_schema.py. Keyword names become attributes on the generated client:

from kankyo import env, url, Schema, secret, boolean, integer, one_of

schema = Schema(
    database_url=env('DATABASE_URL', url()),
    port=env('PORT', integer(minimum=1, maximum=65535), default=8080),
    debug=env('DEBUG', boolean(), optional=True),
    log_level=env('LOG_LEVEL', one_of('debug', 'info', 'warning', 'error'), default='info'),
    api_token=env('API_TOKEN', secret(min_length=1)),
)

Generate the client:

kankyo generate env_schema.py --output config.py

The equivalent module command is python -m kankyo generate env_schema.py --output config.py. Use the client in your application:

from config import load

config = load(environment='production')

config.database_url  # str
config.port          # int
config.debug         # bool | None
config.log_level     # Literal['debug', 'info', 'warning', 'error']
config.api_token     # Secret

token = config.api_token.reveal()

The generated module contains a frozen dataclass, concrete annotations, and load() / load_from() functions. Editors and type checkers can inspect it directly. Commit it alongside your schema and regenerate after schema changes. Its schema is embedded in the generated file, so your application needs Kankyo at runtime but does not need to import or ship env_schema.py. Importing the client does not read environment files or process variables.

See the example schema and its generated client.

Loading priority

load() uses the current directory as its root. Pass root=... to select another directory. Each layer overrides the preceding layers in this order:

Priority Source
1 .env
2 .env.<environment>
3 .env.local
4 .env.<environment>.local
5 os.environ
6 Explicit overrides
config = load(root='settings', environment='production', overrides={'PORT': '9000'})

Environment-specific files are used only when you supply environment. Missing files are fine; malformed or unreadable files raise SourceError, even if a later layer would override their values. Loading never changes os.environ. Keep local environment files out of version control.

Files support UTF-8, an optional BOM, export NAME=value, empty values, comments, and single or double quotes. Unquoted inline comments start with # after whitespace. Quoted values can span lines. Double quotes recognize \n, \r, \t, \", and \\; single quotes preserve backslashes. Values are literal: ${NAME} is not expanded and shell commands are never evaluated. Duplicate keys in a file use the last value.

Fields and defaults

Every field is required unless it has a default or optional=True:

from kankyo import env, Schema, string, boolean, integer

schema = Schema(
    host=env('HOST', string(min_length=1)),
    port=env('PORT', integer(minimum=1), default=8080),
    debug=env('DEBUG', boolean(), optional=True),
    region=env('REGION', string(), optional=True, default='us-east'),
)

An optional field without a default becomes T | None. A field with a default always has type T, including when optional=True is set. Defaults accept Python values or environment strings and are parsed and validated when the schema is defined. Mutable defaults are snapshotted and copied through parsing on each load. Use optional=True for missing values; None is not a default value.

An empty string counts as present. It never activates a default or an alias. Add min_length=1 when a string must be non-empty. Environment mappings must contain strings; Python values are accepted only for schema defaults.

Aliases let a field read from alternative environment variable names:

database_url=env('DATABASE_URL', url(), aliases=('DB_URL',))

The primary name wins when present, followed by aliases in their declared order. An invalid primary value is an error. Use description='...' to include a field comment in the generated dataclass.

Rules

Rule Generated Python type Options / behavior
string() str strip, min_length, max_length, full-match pattern
integer() int Base-10 integers; minimum, maximum
number() float Finite numbers; minimum, maximum
decimal() Decimal Exact finite decimals; minimum, maximum
boolean() bool Case-insensitive true/false, yes/no, on/off, 1/0
one_of('dev', 'prod') Literal['dev', 'prod'] Exact, case-sensitive string choices
duration() timedelta Numeric seconds or compound units: d, h, m, s, ms, us
path() Path Non-empty paths; expands ~; no filesystem existence check
url() str Absolute URL with a host and valid port; optional schemes=('https',)
uuid() UUID UUID strings
ip_address() `IPv4Address IPv6Address`
secret() Secret strip, min_length, max_length; redacted display
list_of(rule) list[T] Typed CSV or JSON arrays; separator, min_length, max_length, unique

String whitespace is preserved unless strip=True. Numeric and boolean strings allow surrounding whitespace. Numeric bounds are inclusive. Duration fractions are truncated to Python's microsecond precision. Paths are relative to the application's current directory, including when dotenv files come from another root.

Lists validate every item, including items in defaults. CSV supports quoted separators, and whitespace after a separator is skipped. Empty input produces an empty list. Nested lists use JSON arrays:

from kankyo import env, Schema, string, integer, list_of

schema = Schema(
    hosts=env('HOSTS', list_of(string(min_length=1), unique=True), default=['localhost']),
    ports=env('PORTS', list_of(integer(minimum=1)), default=[8080, 8081]),
    groups=env('GROUPS', list_of(list_of(integer())), default=[[1, 2], [3]]),
)

Rules are immutable and can be reused across fields. Kankyo deliberately keeps the rule set focused on common configuration values. Application-specific transformations can run after loading.

Validation and secrets

load() and load_from() parse all fields before returning a client. They raise one ConfigError containing every invalid or missing field. Unrelated environment variables are ignored.

from config import load
from kankyo import ConfigError

try:
    config = load()
except ConfigError as error:
    for issue in error.issues:
        print(issue.field, issue.variable, issue.reason)
    raise SystemExit(1)

Errors identify fields and rules without including input values. str(secret), repr(secret), and the generated client's representation redact secrets. Call .reveal() to retrieve the actual string. Redaction is for display; it is not encryption. Schema defaults are embedded in generated source, so real credentials belong in the environment rather than defaults.

SchemaError covers invalid definitions, SourceError covers dotenv failures, and GenerationError covers generation failures. All inherit from KankyoError.

Testing your application

load_from() reads only the supplied mapping. It does not read dotenv files or merge process variables:

from config import load_from

config = load_from({
    'DATABASE_URL': 'postgres://localhost/app',
    'API_TOKEN': 'test-token',
    'PORT': '9000',
})

assert config.port == 9000
assert config.debug is None

The dataclass prevents attribute reassignment. List fields remain ordinary mutable Python lists, with independent values for each load. Constructing the dataclass directly does not run validation; use the generated loaders for environment input.

Generator options

kankyo generate env_schema.py --output app/config.py --class-name Settings
kankyo generate env_schema.py --name production --output app/config.py
kankyo generate env_schema.py --output app/config.py --check

Without --output, the client is written to config_gen.py beside the schema. A file must define exactly one public Schema instance unless --name selects one. Schemas are normal Python files executed during generation, so use files you trust. Put schema files in modules separate from their generated clients.

Generation is deterministic and writes atomically. --check exits with a failure when output is missing or stale and never writes the output. Handwritten files require --force to replace. The schema file itself cannot be used as the output. Don't format generated files separately, since that would cause freshness checks to fail.

For programmatic generation, kankyo.generate exposes load_schema(), render(), and generate().

Development

python -m venv .venv
# Activate .venv using your shell's activation command.
python -m pip install -e '.[dev]'
python -m mypy
python -m kankyo generate examples/env_schema.py --check
python -m pytest
python -m build
python -m twine check --strict dist/*

Tests runs on Linux, Windows, and macOS with Python 3.14. It checks types and generated output, builds the wheel from the sdist, verifies package metadata, and tests the installed wheel outside the checkout with coverage. Distribution and coverage artifacts are retained for 14 days.

Metadata

Release files for kankyo 1.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kankyo 1.0.1
File Size Uploaded Provenance Actions
kankyo-1.0.1.tar.gz 27.3 kB With provenance Details for kankyo-1.0.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kankyo 1.0.1
File Interpreter ABI Platform Actions
kankyo-1.0.1-py3-none-any.whl Python 3 none any Details for kankyo-1.0.1-py3-none-any.whl

Total release size: 46.0 kB

Release files / kankyo-1.0.1.tar.gz

Download URL kankyo-1.0.1.tar.gz
Size 27.3 kB
Tags Source
SHA-256 checksum
How to use checksums
98a5ad4500db7e5ada376982a2bef4c1392f4867a999fd2cff4e353ddedb9667
BLAKE2b-256 checksum
How to use checksums
1548f91f2aaf0287dab65b455fcedc2bc5546c6f33287b25170b9cce13d47e4d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Release files / kankyo-1.0.1-py3-none-any.whl

Download URL kankyo-1.0.1-py3-none-any.whl
Size 18.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8d6945d0e37e700d680a523030c3dfa1822787d647e2e95bd5b80bc479810d2a
BLAKE2b-256 checksum
How to use checksums
2cbeff25a92286e261a070868a359105ae28c36a3621b57250e24e8cc54ef077
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

Security for kankyo 1.0.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for kankyo-1.0.1.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 6, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

1.0.1 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page