Read HOCON config files in Python, to the spec. Fast, because the parser is written in Zig. Familiar, because the API is pyhocon's.
HOCON is JSON made for people: comments, no quotes needed, units like 30s and 512MiB, files that include other files, later settings that merge over earlier ones, and values taken from other keys or from environment variables. One base file, one small file per environment on top.
# app.conf
db {
host = localhost
pool = 4
}
timeout = 30s
cache = 512MiB
retention = 3 months
debug = false
ratio = 0.75
include "prod.conf"
# prod.conf
db.host = db.example.org
db.pool = ${?DB_POOL} # only when DB_POOL is set
from zig_hocon import ConfigFactory
conf = ConfigFactory.parse_file("app.conf")
conf.get_string("db.host") # "db.example.org"
conf.get_int("db.pool") # 4, or $DB_POOL when it is set
The rest of this page reads these two files.
Why zig-hocon
- Correct. Passes 442 of the 445 tests in the conformance suite, one test for each sentence of the spec.*
- Fast. Orders of magnitude faster than what Python usually has.**
- Clear errors. File, line, column, and a caret under the mistake.
- No dependencies. A thin ctypes layer over one bundled library.
* The other three are where the spec leaves room and implementations disagree.
** A 6 KB config in 1 ms, a 10 MB one in 0.3 s, on an Apple M4 Pro.
Install
pip install zig-hocon
One wheel per platform, for every Python from 3.10 on, free-threaded builds and PyPy included: macOS (arm64, x86_64), Linux (x86_64, aarch64; glibc 2.17+ and musl) and Windows (x86_64). The import name is zig_hocon. Anywhere else, build it from source (below).
Beta: the API can still change before 1.0. BSD-3-Clause.
Use
from zig_hocon import ConfigFactory, KEEP
conf = ConfigFactory.parse_file("app.conf") # DB_POOL from os.environ
conf = ConfigFactory.parse_file("app.conf", env={"DB_POOL": "8"}) # from a dict
conf = ConfigFactory.parse_file("app.conf", env=None) # no variables: pool stays 4
conf = ConfigFactory.parse_file("app.conf", env=KEEP) # ${?DB_POOL} kept, for later
conf = ConfigFactory.parse_file("app.conf", resolve=False) # every ${…} as written
conf = ConfigFactory.parse_file("app.conf", http_timeout=5) # an http(s) include waits 5 s, not 30
conf = ConfigFactory.parse_string('include "app.conf"', basedir=".") # text, includes from basedir
conf.get_string("db.host") # "db.example.org"
conf.get_int("db.pool") # 4
conf.get_float("ratio") # 0.75
conf.get_bool("debug") # False: true/yes/on, false/no/off
conf.get_duration("timeout") # timedelta(seconds=30)
conf.get_bytes("cache") # 536870912: "512MB" counts by 1000, "512MiB" by 1024
conf.get_period("retention") # {"years": 0, "months": 3, "days": 0}: a timedelta has no months
conf.get_config("db") # the object at db, read the same way
conf.get_int("db.size", default=10) # 10: nothing at db.size
conf.as_dict() # plain dicts and lists
conf.to_json() # bytes, not str: for msgspec.json.decode
conf.to_hocon() # HOCON text
A path is written as in the file: db.host, a."b.c". A getter's default is its second argument, as in pyhocon; parse_file and parse_string take everything after the path by keyword. The package is typed (py.typed): get_int("a") is an int, get_int("a", None) an int | None. One document can be read from many threads at once.
Errors are ConfigExceptions, with name, file, line, column and source_line:
| exception | when |
|---|---|
ConfigParseException |
the text is not valid HOCON, or an include fails |
ConfigSubstitutionException |
a ${…} with no value, or a cycle |
ConfigMissingException (also a KeyError) |
nothing at the path, or null |
ConfigWrongTypeException (also a ValueError) |
the value is not what the getter wants |
One file to deploy. env=KEEP resolves includes, overrides and references between keys, and leaves the environment variables in; to_hocon() writes the result, to be read on the server with its own environment:
from pathlib import Path
Path("deploy.conf").write_text(ConfigFactory.parse_file("app.conf", env=KEEP).to_hocon())
Durations and sizes have helpers with the same rules as the getters: zig_hocon.parse_duration("30s") gives a timedelta, zig_hocon.parse_bytes("512MiB") an int.
From the shell, python -m zig_hocon [--json] [--env=process|none|keep] app.conf prints the file resolved.
Coming from pyhocon
Change the import: ConfigFactory, the getters and the exception names are the same. What reads differently:
- The tree is not a dict.
conf["db"]["host"]andjson.dumps(conf)fail; takeconf.as_dict()first.as_plain_ordered_dict()isas_dict(). - Fewer methods.
get,get_list,with_fallbackandputare not there: a typed getter, aninclude, or the dict does the job. - Durations stay text.
30sis"30s"inas_dict()until a getter or a model reads it. - Strict syntax. Input pyhocon quietly accepted, such as
a..b = 1ora = b:c, is an error with its line and column. - No
include package(...). It is pyhocon's own; include by path.
The migration guide has every difference, with what to write instead.
Into a model
The package hands over a dict (as_dict()) or JSON bytes (to_json()); a model library does the checking. The same app.conf into the three usual ones.
pydantic reads the dict. A duration is text in the file, "30s", so a BeforeValidator turns it into a timedelta first:
from datetime import timedelta
from typing import Annotated
import zig_hocon
from pydantic import BaseModel, BeforeValidator
Duration = Annotated[timedelta, BeforeValidator(zig_hocon.parse_duration)]
class Db(BaseModel):
host: str
pool: int
class Settings(BaseModel):
db: Db
timeout: Duration
debug: bool
conf = zig_hocon.ConfigFactory.parse_file("app.conf")
settings = Settings.model_validate(conf.as_dict())
msgspec decodes the JSON bytes straight into a Struct. It reads a timedelta itself only from ISO 8601 ("PT30S"), so the field gets a type of its own that msgspec hands to dec_hook. An environment variable is always a string, so strict=False lets DB_POOL=16 into the int:
from datetime import timedelta
import msgspec
import zig_hocon
class Duration(timedelta):
pass
def dec_hook(type, value):
if type is Duration:
d = zig_hocon.parse_duration(value)
return Duration(d.days, d.seconds, d.microseconds)
raise NotImplementedError(type)
class Db(msgspec.Struct):
host: str
pool: int
class Settings(msgspec.Struct):
db: Db
timeout: Duration
debug: bool
conf = zig_hocon.ConfigFactory.parse_file("app.conf")
settings = msgspec.json.decode(conf.to_json(), type=Settings, dec_hook=dec_hook, strict=False)
@dataclass converts no types and does not build Db from a dict, so msgspec builds it, with the Duration, dec_hook and conf from above:
from dataclasses import dataclass
@dataclass
class Db:
host: str
pool: int
@dataclass
class Settings:
db: Db
timeout: Duration
debug: bool
settings = msgspec.json.decode(conf.to_json(), type=Settings, dec_hook=dec_hook, strict=False)
All three give settings.db.host == "db.example.org" and settings.timeout == timedelta(seconds=30), and fail with the model's own error, path included, on a value of the wrong type. A size works the same way through zig_hocon.parse_bytes. The examples have these as tests, with layered environments, errors and one-file deploys.
Building from source
zig build python -Doptimize=ReleaseSafe # libhocon into python/zig_hocon/
pip install python/
zig build test-python # the package's tests, with uv
zig build wheels # a wheel for every platform, into zig-out/wheels/
Needs Zig 0.16. Source, issues and the Zig and C libraries: github.com/lejmr/zig-hocon. BSD-3-Clause.
Metadata
Release files for zig-hocon 0.5.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| zig_hocon-0.5.0-py3-none-win_amd64.whl | Python 3 | none | Windows x86-64 | Details |
| zig_hocon-0.5.0-py3-none-musllinux_1_2_x86_64.whl | Python 3 | none | Linux musl 1.2+ x86-64 | Details |
| zig_hocon-0.5.0-py3-none-musllinux_1_2_aarch64.whl | Python 3 | none | Linux musl 1.2+ ARM64 | Details |
| zig_hocon-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | Python 3 | none | Linux glibc 2.17+ x86-64 | Details |
| zig_hocon-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | Python 3 | none | Linux glibc 2.17+ ARM64 | Details |
| zig_hocon-0.5.0-py3-none-macosx_13_0_x86_64.whl | Python 3 | none | macOS 13.0+ x86-64 | Details |
| zig_hocon-0.5.0-py3-none-macosx_13_0_arm64.whl | Python 3 | none | macOS 13.0+ ARM64 | Details |
Total release size: 3.7 MB
Release files / zig_hocon-0.5.0-py3-none-win_amd64.whl
| Download URL | zig_hocon-0.5.0-py3-none-win_amd64.whl |
|---|---|
| Size | 588.3 kB |
| Tags | Python 3 Windows x86-64 |
|
SHA-256 checksum How to use checksums |
320b9c6124318adb71602297c956e6becda4493e0129a8b97607c79a772b7b81
|
|
BLAKE2b-256 checksum How to use checksums |
5258e2c1349fa78849d23c059948e28a41993f81f3eae8fad311927b20a401bc
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-musllinux_1_2_x86_64.whl
| Download URL | zig_hocon-0.5.0-py3-none-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 539.1 kB |
| Tags | Linux musl 1.2+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
eaefb1d341c19bf85d15f282ce1aaa601c938bed8c2402b355f92bbb32ac7b70
|
|
BLAKE2b-256 checksum How to use checksums |
9b2415cd75132a0f7f8a0d2a6b6e05c8481d4c9a13e7efc25bcec8277482fb8f
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-musllinux_1_2_aarch64.whl
| Download URL | zig_hocon-0.5.0-py3-none-musllinux_1_2_aarch64.whl |
|---|---|
| Size | 522.4 kB |
| Tags | Linux musl 1.2+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
8daa25a90feb6f10529f5ed6d769d24e70b3fd9abccc4e4ba5ed3ee7518f4e00
|
|
BLAKE2b-256 checksum How to use checksums |
11ad765154abb501a17f32385ca865664c8f212d7b6a36419a12f85ba46990ef
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | zig_hocon-0.5.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 540.9 kB |
| Tags | Linux glibc 2.17+ x86-64 Python 3 |
|
SHA-256 checksum How to use checksums |
abc93122909754b783284e0c9b4f5a476bd4a186dc8425e7f69c18765f6d0d1e
|
|
BLAKE2b-256 checksum How to use checksums |
9ce0d98e3bc67dee17702c4c0a2bd93f660a0d7e930e30d50f22141f9c0ee9e8
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | zig_hocon-0.5.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 523.5 kB |
| Tags | Linux glibc 2.17+ ARM64 Python 3 |
|
SHA-256 checksum How to use checksums |
6d443bada4297a088667d057974dc414d699456f2b87c62dfeff3858b710fe95
|
|
BLAKE2b-256 checksum How to use checksums |
f079d79023cb0ff2d5fc27e6f56709aba6bc75afad1c7d1ce62564e42c2c7f66
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-macosx_13_0_x86_64.whl
| Download URL | zig_hocon-0.5.0-py3-none-macosx_13_0_x86_64.whl |
|---|---|
| Size | 518.1 kB |
| Tags | Python 3 macOS 13.0+ x86-64 |
|
SHA-256 checksum How to use checksums |
11bf768f86c78e238c5f8d8196b2c607bb9165eaaedf4239e6f1431fe1e332d1
|
|
BLAKE2b-256 checksum How to use checksums |
0e046be914bc5acd076f6372778dfb81c142a61d155496a04d8093e66701ab49
|
| 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 18, 2026.
Transparency logRelease files / zig_hocon-0.5.0-py3-none-macosx_13_0_arm64.whl
| Download URL | zig_hocon-0.5.0-py3-none-macosx_13_0_arm64.whl |
|---|---|
| Size | 474.2 kB |
| Tags | Python 3 macOS 13.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
3d464cdad950b2cf9d69863917bc668f65623612b24dabe6c3fa8390a9da267c
|
|
BLAKE2b-256 checksum How to use checksums |
d9c0a072b8e704e92043474794062a1555d6008e1934e600fc79687a2e191b05
|
| 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 18, 2026.
Transparency logSecurity for zig-hocon 0.5.0
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 7 files in this release have been uploaded with provenance metadata.
Attestation for zig_hocon-0.5.0-py3-none-win_amd64.whl
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 18, 2026.
Transparency logA 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.