pydantic-parsed-env
Parse common collection settings from simple env strings instead of JSON.
If you want ALLOWED_HOSTS=api.local,worker.local (not
ALLOWED_HOSTS=["api.local","worker.local"]), this package is for you.
Quickstart
pip install pydantic-parsed-env
Requires Python 3.12+.
Runtime dependency policy:
- Published runtime dependencies use tested compatibility ranges, not exact pins.
- Supported runtime dependency ranges are declared in the package metadata.
- Exact versions for development are managed separately in the lockfile/tooling.
from typing import Annotated
from pydantic import Field
from pydantic_settings import SettingsConfigDict
from pydantic_parsed_env import ParseOptions, Parsed, ParsedEnvSettings
class Settings(ParsedEnvSettings):
model_config = SettingsConfigDict(env_prefix="APP_")
hosts: Parsed[list[str]] = Field(default_factory=list[str])
ports: Annotated[
dict[str, int],
ParseOptions(kv_delimiter="="),
] = Field(default_factory=dict[str, int])
# export APP_HOSTS="api.local, worker.local"
# export APP_PORTS="http=80,https=443"
settings = Settings()
assert settings.hosts == ["api.local", "worker.local"]
assert settings.ports == {"http": 80, "https": 443}
In the simple case, you can use Parsed[dict[str, int]] and the default
kv_delimiter=":". The example uses ParseOptions(kv_delimiter="=") only to
show delimiter override.
When to use this
Use this package when you want readable delimiter-based env values for collections.
Use plain pydantic-settings JSON parsing when you need nested objects,
nullable collection items, or other complex shapes.
Why
pydantic-settings is excellent, but structured env values commonly use JSON.
That can be verbose and brittle in shell scripts, Docker env files, and ops
tooling.
pydantic-parsed-env keeps common collection config short and readable:
APP_HOSTS=api.local,worker.localAPP_FEATURE_FLAGS=true,false,trueAPP_PORTS=http:80,https:443
Core API
ParsedEnvSettings:BaseSettingssubclass that wires in the custom env source.Parsed[T]: shorthand forAnnotated[T, ParseOptions()].ParseOptions(...): annotation metadata factory for delimiter-based parsing.
ParseOptions(...) metadata alone is not enough. The custom parser is
installed via settings_customise_sources, so your settings class must inherit
from ParsedEnvSettings.
Supported parsing
ParseOptions(...) applies to:
list[T]set[T]tuple[T, ...]and fixed tuples liketuple[str, int]dict[K, V](uses defaultkv_delimiterset to:)
Supported element conversion:
str,int,float,bool(true/false)Enum/StrEnumLiteral[...]
Fields without ParseOptions(...) keep normal pydantic-settings behavior,
including JSON parsing for complex values.
Behavior matrix
| Field type | Example input | Parsed result |
|---|---|---|
list[int] |
"1,2,3" |
[1, 2, 3] |
set[str] |
"a,a,b" |
{"a", "b"} |
tuple[float, ...] |
"1.2,3.4" |
(1.2, 3.4) |
tuple[str, int] |
"host,80" |
("host", 80) |
dict[str, int] + kv_delimiter=":" |
"http:80,https:443" |
{"http": 80, "https": 443} |
Empty and malformed input semantics
-
For collection fields, unset and
""map to empty collections:list[T] -> []set[T] -> set()tuple[T, ...] -> ()dict[K, V] -> {}
-
For required fields without defaults, unset values still follow normal
pydantic-settingsrequired-field behavior. -
Noneis not inferred from empty input by default. If you need nullable collection values, use an explicit sentinel convention. -
Parsing is strict for malformed segments:
"a,,b"is invalid forlist[int]and similar non-string item types."k1:v1,broken,k2:v2"is invalid fordict[K, V].
-
Empty segments are allowed for
stritems:list[str]:"a,,b" -> ["a", "", "b"]
Dict parsing rules
- Dict fields require a key-value delimiter, for example
ParseOptions(kv_delimiter=":"). - Each pair must match
key<kv_delimiter>value, for example"k:v". - Whitespace around keys and values is trimmed before conversion.
- Duplicate keys use the last value encountered:
"a:1,a:2" -> {"a": 2}
Error behavior
At the settings integration layer, parsing errors are raised as
pydantic_settings.SettingsError (matching upstream source behavior).
Detailed parser failure context is preserved in SettingsError.__cause__.
Non-goals and limits
- Complex nested model elements (for example
list[MyModel]) are not supported by simple string parsing. - Nullable item types inside collections (for example
list[int | None]ordict[str, bool | None]) are intentionally out of scope for simple parsing. Use standard JSON-basedpydantic-settingsparsing for those shapes. - Complex item-level unions (including nullable item unions) are not supported for simple parsing.
- Applying
ParseOptions(...)to non-collection fields is a type error.
Development
Default (no Nix required):
uv sync
uv run ruff check .
uv run ruff format .
uv run pyright .
uv run pytest -q
If you use Nix, a dev shell plus formatting/check wiring is already provided:
nix develop
nix fmt
nix flake check
CI runs the same Nix commands (nix fmt and nix flake check) using
Determinate Nix + Magic Nix Cache.
Versioning and releases
- Package versions are derived from Git tags via
hatch-vcs. - Release tags must use the
vX.Y.Zform, for examplev0.3.1. - The published package version strips the
vprefix, sov0.3.1becomes0.3.1on PyPI. - This project follows semantic versioning for its public API. While the
package is still
0.x, breaking changes may still land in minor releases.
License
Apache-2.0.
Release files for pydantic-parsed-env 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| pydantic_parsed_env-0.1.0.tar.gz | 45.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pydantic_parsed_env-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.3 kB
Release files / pydantic_parsed_env-0.1.0.tar.gz
| Download URL | pydantic_parsed_env-0.1.0.tar.gz |
|---|---|
| Size | 45.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1eba464e1a1edeaa39e228a16d0d24de9583d908b0dfd9350b962244ff69671b
|
|
BLAKE2b-256 checksum How to use checksums |
6f4c777a842b0aed69f767ec3605e86448ecb4d551f7ca23928f14ee99ef011c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Jul 10, 2026.
Transparency logRelease files / pydantic_parsed_env-0.1.0-py3-none-any.whl
| Download URL | pydantic_parsed_env-0.1.0-py3-none-any.whl |
|---|---|
| Size | 14.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4148bdd2890d4bec6b4077e529eda07c812b08eeb54c536f5421fad3f74be829
|
|
BLAKE2b-256 checksum How to use checksums |
9db54aac8bac72cc02c1f6e1e7eca95903691df70bf106828817e88c07398c00
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
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 Jul 10, 2026.
Transparency log