hocon-parser 1.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hocon/__init__.py +68 -0
- hocon/_internal/__init__.py +4 -0
- hocon/_internal/lexer/__init__.py +1 -0
- hocon/_internal/lexer/lexer.py +500 -0
- hocon/_internal/lexer/token.py +65 -0
- hocon/_internal/parser/__init__.py +1 -0
- hocon/_internal/parser/ast.py +120 -0
- hocon/_internal/parser/empty_check.py +29 -0
- hocon/_internal/parser/parser.py +533 -0
- hocon/_internal/properties/__init__.py +4 -0
- hocon/_internal/properties/properties.py +84 -0
- hocon/_internal/resolver/__init__.py +1 -0
- hocon/_internal/resolver/fold_self_ref.py +282 -0
- hocon/_internal/resolver/include_loader.py +252 -0
- hocon/_internal/resolver/resolver.py +95 -0
- hocon/_internal/resolver/structure_builder.py +184 -0
- hocon/_internal/resolver/substitution_resolver.py +407 -0
- hocon/_internal/resolver/types.py +162 -0
- hocon/_internal/resolver/utils.py +144 -0
- hocon/coerce.py +257 -0
- hocon/config.py +524 -0
- hocon/errors.py +71 -0
- hocon/numeric_array.py +51 -0
- hocon/parse.py +122 -0
- hocon/py.typed +0 -0
- hocon/value.py +111 -0
- hocon/value_factory.py +71 -0
- hocon_parser-1.8.0.dist-info/METADATA +493 -0
- hocon_parser-1.8.0.dist-info/RECORD +31 -0
- hocon_parser-1.8.0.dist-info/WHEEL +4 -0
- hocon_parser-1.8.0.dist-info/licenses/LICENSE +202 -0
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hocon-parser
|
|
3
|
+
Version: 1.8.0
|
|
4
|
+
Summary: Full Lightbend HOCON specification-compliant parser for Python
|
|
5
|
+
Project-URL: Repository, https://github.com/o3co/py.hocon
|
|
6
|
+
Project-URL: Issues, https://github.com/o3co/py.hocon/issues
|
|
7
|
+
Author: 1o1 Co. Ltd.
|
|
8
|
+
License-Expression: Apache-2.0
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: config,configuration,hocon,parser
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Typing :: Typed
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# py.hocon — HOCON Parser for Python
|
|
22
|
+
|
|
23
|
+
[](LICENSE)
|
|
24
|
+
[](https://www.python.org/)
|
|
25
|
+
[](docs/spec-compliance.md)
|
|
26
|
+
|
|
27
|
+
A [Lightbend HOCON specification](https://github.com/lightbend/config/blob/main/HOCON.md)
|
|
28
|
+
parser for Python. Hand-written lexer, recursive-descent parser, and a typed `Config`
|
|
29
|
+
API. Zero runtime dependencies (pure stdlib), Python 3.11+, fully typed (`py.typed`).
|
|
30
|
+
See [Spec Compliance](#spec-compliance) for the current conformance rate.
|
|
31
|
+
|
|
32
|
+
> **Implemented by [Claude](https://claude.ai/) (Anthropic)** — designed and built
|
|
33
|
+
> end-to-end with Claude Code, ported from the sibling
|
|
34
|
+
> [ts.hocon](https://github.com/o3co/ts.hocon) implementation.
|
|
35
|
+
|
|
36
|
+
[日本語](README.ja.md)
|
|
37
|
+
|
|
38
|
+
> **Library stance** — py.hocon is a HOCON config loader. Its purpose is reading
|
|
39
|
+
> `.conf` config files and providing typed access via the `Config` API
|
|
40
|
+
> (`get_string`, `get_number`, `get_boolean`, `get_duration`, `get_bytes`,
|
|
41
|
+
> `get_period`, `to_object`). It is not a low-level parser API — internal types under
|
|
42
|
+
> `hocon._internal` may change between minor versions.
|
|
43
|
+
>
|
|
44
|
+
> **Cross-language conformance** — This implementation is tested against shared
|
|
45
|
+
> expected-JSON fixtures from [o3co/xx.hocon](https://github.com/o3co/xx.hocon)
|
|
46
|
+
> alongside [ts.hocon](https://github.com/o3co/ts.hocon),
|
|
47
|
+
> [go.hocon](https://github.com/o3co/go.hocon), and
|
|
48
|
+
> [rs.hocon](https://github.com/o3co/rs.hocon), ensuring all four implementations
|
|
49
|
+
> meet the same Lightbend HOCON specification.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
### 1. Install
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pip install hocon-parser
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The import name is `hocon`:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
import hocon
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. Use
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
import hocon
|
|
71
|
+
|
|
72
|
+
cfg = hocon.parse("""
|
|
73
|
+
server {
|
|
74
|
+
host = "localhost"
|
|
75
|
+
port = 8080
|
|
76
|
+
}
|
|
77
|
+
database {
|
|
78
|
+
url = "jdbc:postgresql://localhost/mydb"
|
|
79
|
+
pool-size = 10
|
|
80
|
+
}
|
|
81
|
+
""")
|
|
82
|
+
|
|
83
|
+
cfg.get_string("server.host") # "localhost"
|
|
84
|
+
cfg.get_int("server.port") # 8080
|
|
85
|
+
cfg.has("server.host") # True
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Why HOCON?
|
|
89
|
+
|
|
90
|
+
| | `.env` | JSON | YAML | HOCON |
|
|
91
|
+
|---|---|---|---|---|
|
|
92
|
+
| Comments | No | No | Yes | Yes |
|
|
93
|
+
| Nesting | No | Yes | Yes | Yes |
|
|
94
|
+
| References / Substitution | No | No | No | Yes (`${var}`) |
|
|
95
|
+
| File inclusion | No | No | No | Yes (`include`) |
|
|
96
|
+
| Object merging | No | No | Anchors (fragile) | Yes (deep merge) |
|
|
97
|
+
| Optional values | No | No | No | Yes (`${?var}`) |
|
|
98
|
+
| Trailing commas | N/A | No | N/A | Yes |
|
|
99
|
+
| Unquoted strings | Yes | No | Yes | Yes |
|
|
100
|
+
|
|
101
|
+
HOCON isn't just a serialization format — it's a **config-injection language**.
|
|
102
|
+
JSON, YAML, and TOML describe data structures and leave file layering, environment
|
|
103
|
+
variables, and reference resolution to your code (Pydantic, attrs, etc.). HOCON
|
|
104
|
+
bakes those into the spec itself: by the time your program reads the config,
|
|
105
|
+
fallback files are merged and `${VAR}` references resolved into a single composed
|
|
106
|
+
object. Conditional branching from "is this value present in this layer?"
|
|
107
|
+
disappears at the format boundary.
|
|
108
|
+
|
|
109
|
+
On top of that, HOCON combines the readability of YAML with the structure of JSON —
|
|
110
|
+
making it a strong fit for anything beyond flat key-value config.
|
|
111
|
+
|
|
112
|
+
## Features
|
|
113
|
+
|
|
114
|
+
- Full HOCON parsing: objects, arrays, scalars, substitutions (`${path}`, `${?path}`)
|
|
115
|
+
- Self-referential substitutions (`path = ${path}:/extra`) with cycle detection
|
|
116
|
+
- Deep-merge for duplicate keys (last definition wins)
|
|
117
|
+
- `+=` append operator
|
|
118
|
+
- `include` directives: `include "file.conf"`, `include file("...")`,
|
|
119
|
+
`include package("id", "file")`, and `include required(...)` wrappers
|
|
120
|
+
- Triple-quoted strings (`"""..."""`)
|
|
121
|
+
- Duration, period, and byte-size parsing (`get_duration()`, `get_period()`,
|
|
122
|
+
`get_bytes()`)
|
|
123
|
+
- Environment-variable substitution (`${HOME}`) and env-var list expansion
|
|
124
|
+
(`${NAME[]}` → `NAME_0`, `NAME_1`, …)
|
|
125
|
+
- Numerically-keyed object → array conversion
|
|
126
|
+
- `.properties` includes
|
|
127
|
+
- Deferred resolution lifecycle: `parse(..., resolve_substitutions=False)` →
|
|
128
|
+
`with_fallback` → `resolve()` (Lightbend `parseString` / `withFallback` /
|
|
129
|
+
`resolve()` API)
|
|
130
|
+
- Zero runtime dependencies (pure stdlib), fully typed (`py.typed`)
|
|
131
|
+
|
|
132
|
+
## API Reference
|
|
133
|
+
|
|
134
|
+
### Parse functions
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
import hocon
|
|
138
|
+
|
|
139
|
+
hocon.parse(text, *, base_dir=None, env=None, read_file=None,
|
|
140
|
+
resolve_substitutions=True, origin_description=None,
|
|
141
|
+
resolve_from=None, package_resolver=None) -> hocon.Config
|
|
142
|
+
hocon.parse_string(text, **opts) # alias of parse()
|
|
143
|
+
hocon.parse_file(path, **opts) # resolves includes relative to the file's dir
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Parse options (keyword-only):
|
|
147
|
+
|
|
148
|
+
| Option | Type | Description |
|
|
149
|
+
|--------|------|-------------|
|
|
150
|
+
| `base_dir` | `str` | Base directory for `include` resolution |
|
|
151
|
+
| `env` | `dict[str, str]` | Environment variables for substitution (default: `os.environ`) |
|
|
152
|
+
| `read_file` | `(str) -> str` | Custom file reader |
|
|
153
|
+
| `resolve_substitutions` | `bool` | Resolve substitutions during parse (default `True`); `False` returns a deferred `Config` |
|
|
154
|
+
| `origin_description` | `str` | Source name surfaced in error messages |
|
|
155
|
+
| `resolve_from` / `package_resolver` | — | Control `include package(...)` resolution |
|
|
156
|
+
|
|
157
|
+
### Config methods
|
|
158
|
+
|
|
159
|
+
All typed getters raise on failure; paths use dot notation, with quoted
|
|
160
|
+
segments for keys that contain dots (`config.get_string('"a.b".c')`).
|
|
161
|
+
|
|
162
|
+
| Method | Returns | Raises if |
|
|
163
|
+
|--------|---------|-----------|
|
|
164
|
+
| `get(path)` | value or `None` | — |
|
|
165
|
+
| `get_string(path)` | `str` | missing, wrong type, or unresolved |
|
|
166
|
+
| `get_number(path)` | `int \| float` | missing, not numeric, or unresolved |
|
|
167
|
+
| `get_int(path)` | `int` | missing, not numeric, or unresolved |
|
|
168
|
+
| `get_float(path)` | `float` | missing, not numeric, or unresolved |
|
|
169
|
+
| `get_boolean(path)` | `bool` | missing, wrong type, or unresolved |
|
|
170
|
+
| `get_duration(path, unit=None)` | `float` | missing, wrong type, or invalid duration |
|
|
171
|
+
| `get_bytes(path, unit=None)` | `float` | missing, wrong type, or invalid byte size |
|
|
172
|
+
| `get_period(path)` | `Period` | missing, wrong type, or invalid period |
|
|
173
|
+
| `get_config(path)` | `Config` | missing, not an object, or unresolved |
|
|
174
|
+
| `get_list(path)` | `list` | missing, not an array, or unresolved |
|
|
175
|
+
| `get_value(path)` | `HoconValue \| None` | subtree unresolved |
|
|
176
|
+
| `has(path)` | `bool` | — |
|
|
177
|
+
| `keys()` | `list[str]` | — |
|
|
178
|
+
| `with_fallback(fallback)` | `Config` | — |
|
|
179
|
+
| `resolve(*, allow_unresolved=False, use_system_environment=True)` | `Config` | unresolvable substitution (unless `allow_unresolved`) |
|
|
180
|
+
| `resolve_with(source, *, ...)` | `Config` | source unresolved, or unresolvable substitution |
|
|
181
|
+
| `is_resolved()` | `bool` | — |
|
|
182
|
+
| `to_object()` | `dict / list / scalar` | — |
|
|
183
|
+
|
|
184
|
+
`get_boolean` also accepts `yes`/`no` and `on`/`off`. `get_number` returns an
|
|
185
|
+
`int` for integral lexemes and a `float` otherwise.
|
|
186
|
+
|
|
187
|
+
### Value factories
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
from hocon import from_map, empty
|
|
191
|
+
|
|
192
|
+
cfg = from_map({"server": {"host": "localhost", "port": 8080}})
|
|
193
|
+
cfg.get_int("server.port") # 8080
|
|
194
|
+
|
|
195
|
+
empty() # a resolved Config with no keys
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Keys in `from_map` are treated as plain keys, **not** path expressions —
|
|
199
|
+
`{"a.b": 1}` produces a top-level key literally named `a.b`.
|
|
200
|
+
|
|
201
|
+
### Structural access
|
|
202
|
+
|
|
203
|
+
Beyond the decoded `to_object()` / `get()`, `get_value()` exposes the raw value
|
|
204
|
+
tree for introspection via standalone accessors:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from hocon import as_string, as_object, is_scalar, is_null
|
|
208
|
+
|
|
209
|
+
node = cfg.get_value("server") # HoconValue
|
|
210
|
+
as_object(node) # dict[str, HoconValue] | None
|
|
211
|
+
is_scalar(cfg.get_value("server.port")) # True
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### Deferred resolution
|
|
215
|
+
|
|
216
|
+
Separate parse, fallback-layering, and resolve for runtime config injection:
|
|
217
|
+
|
|
218
|
+
```python
|
|
219
|
+
import hocon
|
|
220
|
+
from hocon import from_map
|
|
221
|
+
|
|
222
|
+
# 1. Parse without resolving — substitutions deferred
|
|
223
|
+
cfg = hocon.parse(
|
|
224
|
+
'version = ${shortversion}-${CI_RUN_NUMBER}\n'
|
|
225
|
+
'variables { shortversion = "1.2.3" }',
|
|
226
|
+
resolve_substitutions=False,
|
|
227
|
+
)
|
|
228
|
+
cfg.is_resolved() # False — ${CI_RUN_NUMBER} still pending
|
|
229
|
+
|
|
230
|
+
# 2. Layer runtime fallbacks
|
|
231
|
+
runtime = from_map({"CI_RUN_NUMBER": "42"})
|
|
232
|
+
variables = cfg.get_config("variables")
|
|
233
|
+
merged = cfg.with_fallback(runtime).with_fallback(variables)
|
|
234
|
+
|
|
235
|
+
# 3. Resolve the full fallback stack
|
|
236
|
+
resolved = merged.resolve(use_system_environment=False)
|
|
237
|
+
resolved.get_string("version") # "1.2.3-42"
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
`resolve_with` resolves the receiver using a source for lookup **without** merging
|
|
241
|
+
the source's keys into the result:
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
receiver = hocon.parse("r = ${key}", resolve_substitutions=False)
|
|
245
|
+
source = from_map({"key": "val"})
|
|
246
|
+
result = receiver.resolve_with(source)
|
|
247
|
+
result.has("key") # False — source keys excluded
|
|
248
|
+
result.get_string("r") # "val"
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Error Types
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
from hocon import (
|
|
255
|
+
ParseError, # lexing/parsing failure: .line, .col, .file
|
|
256
|
+
ResolveError, # substitution/include failure: .path, .line, .col, .file
|
|
257
|
+
PackageLookupError, # include package(...) not found (subclass of ResolveError)
|
|
258
|
+
ConfigError, # wrong type or missing path: .path
|
|
259
|
+
NotResolvedError, # getter on an unresolved path (subclass of ConfigError)
|
|
260
|
+
)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
| Type | When |
|
|
264
|
+
|------|------|
|
|
265
|
+
| `ParseError` | Syntax errors during lexing/parsing (includes line and column) |
|
|
266
|
+
| `ResolveError` | Substitution failures, cyclic references, missing required includes |
|
|
267
|
+
| `PackageLookupError` | `include package(...)` could not be located |
|
|
268
|
+
| `ConfigError` | Missing keys or type mismatches during value access |
|
|
269
|
+
| `NotResolvedError` | Getter called on a path still holding an unresolved substitution |
|
|
270
|
+
|
|
271
|
+
## HOCON Examples
|
|
272
|
+
|
|
273
|
+
```hocon
|
|
274
|
+
# Comments with # or //
|
|
275
|
+
database {
|
|
276
|
+
host = "db.example.com"
|
|
277
|
+
port = 5432
|
|
278
|
+
url = "jdbc:"${database.host}":"${database.port}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# Duplicate keys deep-merge (last wins for scalars)
|
|
282
|
+
server { host = localhost }
|
|
283
|
+
server { port = 8080 } // result: { host: "localhost", port: 8080 }
|
|
284
|
+
|
|
285
|
+
# Self-referential append
|
|
286
|
+
path = "/usr/bin"
|
|
287
|
+
path = ${path}":/usr/local/bin"
|
|
288
|
+
|
|
289
|
+
# += shorthand
|
|
290
|
+
items = [1]
|
|
291
|
+
items += 2
|
|
292
|
+
items += 3 // [1, 2, 3]
|
|
293
|
+
|
|
294
|
+
# Include
|
|
295
|
+
include "defaults.conf"
|
|
296
|
+
include file("overrides.conf")
|
|
297
|
+
|
|
298
|
+
# Triple-quoted multiline strings
|
|
299
|
+
description = """
|
|
300
|
+
This is a
|
|
301
|
+
multiline string.
|
|
302
|
+
"""
|
|
303
|
+
|
|
304
|
+
# Unquoted strings
|
|
305
|
+
path = /usr/local/bin
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Durations, Periods, and Byte Sizes
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
from hocon import Period
|
|
312
|
+
|
|
313
|
+
c = hocon.parse("""
|
|
314
|
+
timeout = "30s"
|
|
315
|
+
cache-ttl = "5m"
|
|
316
|
+
retention = "2w"
|
|
317
|
+
max-size = "512MiB"
|
|
318
|
+
""")
|
|
319
|
+
|
|
320
|
+
c.get_duration("timeout") # 30000.0 (ms)
|
|
321
|
+
c.get_duration("timeout", "s") # 30.0
|
|
322
|
+
c.get_duration("cache-ttl", "m") # 5.0
|
|
323
|
+
|
|
324
|
+
c.get_period("retention") # Period(years=0, months=0, days=14)
|
|
325
|
+
|
|
326
|
+
c.get_bytes("max-size") # 536870912 (bytes)
|
|
327
|
+
c.get_bytes("max-size", "MiB") # 512.0
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Supported duration units: `ns`, `us`, `ms`, `s`, `m`, `h`, `d` (and long forms
|
|
331
|
+
like `seconds`, `minutes`). Duration unit names are **case-sensitive** and must be
|
|
332
|
+
lowercase (HOCON spec S19.8). Byte units are more case-tolerant: the canonical
|
|
333
|
+
forms plus lowercase aliases (`kb`, `kib`, …), any-case long forms (`megabytes`),
|
|
334
|
+
and single-letter powers-of-two in both cases (`K`/`k`, per Lightbend, S21.4).
|
|
335
|
+
Supported byte units: `B`, `KB`/`KiB`, `MB`/`MiB`, `GB`/`GiB`, `TB`/`TiB`.
|
|
336
|
+
|
|
337
|
+
`get_period` (spec S20.1–S20.4) returns a `Period(years, months, days)` value —
|
|
338
|
+
a frozen dataclass mirroring rs.hocon's `Period` struct. Supported units:
|
|
339
|
+
`d`/`day`/`days` (default for bare numbers), `w`/`week`/`weeks` (folded into
|
|
340
|
+
days), `m`/`mo`/`month`/`months`, `y`/`year`/`years` — lowercase only, like
|
|
341
|
+
durations. Periods are **integer-only** (Lightbend `Integer.parseInt`):
|
|
342
|
+
fractional values such as `"7.5"` raise `ConfigError`, unlike `get_duration` /
|
|
343
|
+
`get_bytes` which accept them. Negative periods are permitted.
|
|
344
|
+
|
|
345
|
+
## Performance
|
|
346
|
+
|
|
347
|
+
Indicative timings from `benchmarks/bench.py` (each iteration parses and does a
|
|
348
|
+
`get_string` lookup). Run `make bench` to reproduce on your machine.
|
|
349
|
+
|
|
350
|
+
| Scenario | ops/sec | Time per op |
|
|
351
|
+
|---|---|---|
|
|
352
|
+
| Small config (10 keys) | ~5,700 | ~175 µs |
|
|
353
|
+
| Medium config (100 keys) | ~600 | ~1.7 ms |
|
|
354
|
+
| Large config (1,000 keys) | ~57 | ~17.7 ms |
|
|
355
|
+
| 10 substitutions | ~3,700 | ~270 µs |
|
|
356
|
+
| 50 substitutions | ~800 | ~1.2 ms |
|
|
357
|
+
| 100 substitutions | ~400 | ~2.5 ms |
|
|
358
|
+
| Depth 5 nesting | ~8,500 | ~117 µs |
|
|
359
|
+
| Depth 10 nesting | ~4,600 | ~220 µs |
|
|
360
|
+
| Depth 20 nesting | ~2,000 | ~500 µs |
|
|
361
|
+
|
|
362
|
+
As a pure-Python parser, py.hocon is roughly 30–40× slower than the compiled /
|
|
363
|
+
V8-backed siblings (go.hocon, rs.hocon, ts.hocon). For typical application configs
|
|
364
|
+
(loaded once at startup), the cost is negligible — even a 1,000-key config parses
|
|
365
|
+
in under 20 ms. If you parse very large configs on a hot path, cache the resulting
|
|
366
|
+
`Config`.
|
|
367
|
+
|
|
368
|
+
## Spec Compliance
|
|
369
|
+
|
|
370
|
+
Conformance against the [Lightbend HOCON specification](https://github.com/lightbend/config/blob/main/HOCON.md)
|
|
371
|
+
is tracked at item granularity in [`docs/spec-compliance.md`](docs/spec-compliance.md);
|
|
372
|
+
see [`xx.hocon/docs/compliance-matrix.md`](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md)
|
|
373
|
+
for live cross-impl values.
|
|
374
|
+
|
|
375
|
+
| Corpus | py.hocon | go.hocon / rs.hocon (reference) |
|
|
376
|
+
|---|---:|---:|
|
|
377
|
+
| Spec corpus (134) | **134 (100.0%)** | 134 (100.0%) |
|
|
378
|
+
| Lightbend suite (16) | **14/16** | 14/16 |
|
|
379
|
+
|
|
380
|
+
At parity with the reference sibling implementations. The 2 held-out Lightbend
|
|
381
|
+
fixtures reference JVM system properties (`${?java.version}` / `${?user.home}`),
|
|
382
|
+
which resolve only inside a JVM — every non-JVM parser caps at 14/16.
|
|
383
|
+
|
|
384
|
+
## Related Projects
|
|
385
|
+
|
|
386
|
+
| Project | Language | Registry | Description |
|
|
387
|
+
|---------|----------|----------|-------------|
|
|
388
|
+
| [ts.hocon](https://github.com/o3co/ts.hocon) | TypeScript | [npm](https://www.npmjs.com/package/@o3co/ts.hocon) | HOCON parser for TypeScript/Node.js |
|
|
389
|
+
| [go.hocon](https://github.com/o3co/go.hocon) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/go.hocon) | HOCON parser for Go |
|
|
390
|
+
| [rs.hocon](https://github.com/o3co/rs.hocon) | Rust | [crates.io](https://crates.io/crates/hocon-parser) | HOCON parser for Rust |
|
|
391
|
+
| [hocon2](https://github.com/o3co/hocon2) | Go | [pkg.go.dev](https://pkg.go.dev/github.com/o3co/hocon2) | HOCON → JSON/YAML/TOML/Properties CLI |
|
|
392
|
+
|
|
393
|
+
All four parser implementations are tracked against the same Lightbend HOCON spec —
|
|
394
|
+
see the [cross-impl roll-up](https://github.com/o3co/xx.hocon/blob/main/docs/compliance-matrix.md)
|
|
395
|
+
for per-impl conformance rates.
|
|
396
|
+
|
|
397
|
+
## Best Practices
|
|
398
|
+
|
|
399
|
+
### Config Structure
|
|
400
|
+
|
|
401
|
+
- **Split by domain**: separate configuration into logical units (`database.conf`,
|
|
402
|
+
`server.conf`, `logging.conf`)
|
|
403
|
+
- **Use `include` for composition**: compose a full config from domain-specific files
|
|
404
|
+
- **Avoid logic in config**: HOCON is for declarative data, not conditionals or computation
|
|
405
|
+
|
|
406
|
+
### Environment Variables
|
|
407
|
+
|
|
408
|
+
- **Minimize `${ENV}` usage**: prefer `${?ENV}` (optional) with sensible defaults
|
|
409
|
+
defined in the config itself
|
|
410
|
+
- **Never require env vars for local development**: defaults should work out of the box
|
|
411
|
+
- **Document required env vars**: list them in your project's README or a `.env.example`
|
|
412
|
+
|
|
413
|
+
### Dev / Prod Separation
|
|
414
|
+
|
|
415
|
+
```text
|
|
416
|
+
config/
|
|
417
|
+
├── application.conf # shared defaults
|
|
418
|
+
├── dev.conf # include "application.conf" + dev overrides
|
|
419
|
+
└── prod.conf # include "application.conf" + prod overrides
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### Validation
|
|
423
|
+
|
|
424
|
+
Validate config at application startup, not at point-of-use. Load into a typed
|
|
425
|
+
structure (dataclass, `attrs`, or Pydantic) so errors surface early:
|
|
426
|
+
|
|
427
|
+
```python
|
|
428
|
+
from dataclasses import dataclass
|
|
429
|
+
import hocon
|
|
430
|
+
|
|
431
|
+
@dataclass
|
|
432
|
+
class ServerConfig:
|
|
433
|
+
host: str
|
|
434
|
+
port: int
|
|
435
|
+
|
|
436
|
+
cfg = hocon.parse_file("application.conf")
|
|
437
|
+
server = ServerConfig(
|
|
438
|
+
host=cfg.get_string("server.host"),
|
|
439
|
+
port=cfg.get_int("server.port"),
|
|
440
|
+
) # fails fast on startup if a field is missing or the wrong type
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
## Known Limitations
|
|
444
|
+
|
|
445
|
+
- **`include url(...)`** is not supported. Fetching remote configuration is outside
|
|
446
|
+
the scope of this parser — fetch the content with your HTTP client, then pass it
|
|
447
|
+
to `parse()`.
|
|
448
|
+
- **`include classpath(...)`** is not supported. This is a JVM-specific include form
|
|
449
|
+
with no equivalent outside Java runtimes.
|
|
450
|
+
- **`include package(...)`** resolves via a filesystem convention (search
|
|
451
|
+
`resolve_from` / `base_dir` / CWD for `<base>/<id>/<file>`), not a Python
|
|
452
|
+
package-manager lookup. Supply a custom `package_resolver` for other schemes.
|
|
453
|
+
- **No watch/reload** — the library parses config at load time. For live-reloading,
|
|
454
|
+
re-call `parse()` / `parse_file()` on change.
|
|
455
|
+
- **No streaming parser** — the entire input is loaded into memory. For untrusted
|
|
456
|
+
input, validate size before parsing (see Security Considerations).
|
|
457
|
+
- **`.properties` include** — supports basic `key=value` / `key:value` syntax. Does
|
|
458
|
+
not support multiline values (backslash continuation), Unicode escapes, or key
|
|
459
|
+
escaping from the full Java `.properties` specification.
|
|
460
|
+
|
|
461
|
+
## Security Considerations
|
|
462
|
+
|
|
463
|
+
When parsing untrusted HOCON input, be aware of:
|
|
464
|
+
|
|
465
|
+
- **Path traversal in includes:** `include "../../../etc/passwd"` resolves relative
|
|
466
|
+
to `base_dir`. Supply a custom `read_file` that validates paths if parsing
|
|
467
|
+
untrusted input.
|
|
468
|
+
- **Input size:** the parser has no built-in input size limit. For untrusted input,
|
|
469
|
+
validate size before calling `parse()`.
|
|
470
|
+
- **Include depth:** limited to 50 levels to prevent stack overflow from deep
|
|
471
|
+
include chains.
|
|
472
|
+
|
|
473
|
+
## Development
|
|
474
|
+
|
|
475
|
+
```sh
|
|
476
|
+
make setup # create .venv and install dev dependencies (needs python3.11+)
|
|
477
|
+
make check # ruff + mypy --strict + pytest
|
|
478
|
+
make bench # run the micro-benchmarks
|
|
479
|
+
make testdata # sync the conformance corpus from o3co/xx.hocon
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for the full workflow.
|
|
483
|
+
|
|
484
|
+
## License
|
|
485
|
+
|
|
486
|
+
Apache License 2.0 — see [LICENSE](LICENSE).
|
|
487
|
+
|
|
488
|
+
Copyright 2026 1o1 Co. Ltd.
|
|
489
|
+
|
|
490
|
+
## Attribution
|
|
491
|
+
|
|
492
|
+
Designed and built end-to-end with [Claude Code](https://claude.ai/claude-code),
|
|
493
|
+
ported from [ts.hocon](https://github.com/o3co/ts.hocon).
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
hocon/__init__.py,sha256=PXyTgZkR1-vBqphUpaJse9P6YRUIq8H1V67vvhcks58,1395
|
|
2
|
+
hocon/coerce.py,sha256=tX-c1NaXnX0BQO5mJaDfH0Rhvwa-zpnmah_RB3M7LyE,8522
|
|
3
|
+
hocon/config.py,sha256=pRcKw-tQBCoFKmpnYM7o5df6wW3Hq7yvhUlMqMljz0k,19683
|
|
4
|
+
hocon/errors.py,sha256=hXyM42oC8LXia8UGp91cqXUrHTetrbYKkjtsSdi4fGk,2264
|
|
5
|
+
hocon/numeric_array.py,sha256=xgNEurKshzgN2hdliVr2VuweJwWPp8gm4dq9AzQN0FI,1659
|
|
6
|
+
hocon/parse.py,sha256=waICCDYLrJMDTsazcMaxZROybUWRMB3QqCxA1DQlBd4,4189
|
|
7
|
+
hocon/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
+
hocon/value.py,sha256=M07LXtnWq6JBnfEDWTIAUCiFbhdCdigIzyFIjLdp5ZA,3110
|
|
9
|
+
hocon/value_factory.py,sha256=Ht4HVuHitnUVlSknbHgRDLhkr2GPEAcL5g5uPu0o3fU,2565
|
|
10
|
+
hocon/_internal/__init__.py,sha256=5leFIwnOAla6wYE93-OPZRN0rRxON97WvHW6CJ1fFk0,138
|
|
11
|
+
hocon/_internal/lexer/__init__.py,sha256=_83Pp2hIegIwb2V6oA9rrDYra4wSY_xe16dufESkgNY,67
|
|
12
|
+
hocon/_internal/lexer/lexer.py,sha256=9tis6ahcY2IbYjpeDuK00fM_z1EBNPPoZTRrE46cl4E,18727
|
|
13
|
+
hocon/_internal/lexer/token.py,sha256=nyh4wt62W2gP0ikdmwYl4cpGdz_ECdKTO-sjYSyX4Pk,1910
|
|
14
|
+
hocon/_internal/parser/__init__.py,sha256=5iSwh4zGJ6RyWNlbiRghW7z_v-ClZywaRNlZF75h0Ok,69
|
|
15
|
+
hocon/_internal/parser/ast.py,sha256=dQ9sAFyQeIuf0DiQeR-BZC3cC5NJbgPnEypNWDAHAdU,3160
|
|
16
|
+
hocon/_internal/parser/empty_check.py,sha256=y67IWcxzgzqVisUiVFkqQ-WZLnQDZSCOg9QT6-ILDm4,1046
|
|
17
|
+
hocon/_internal/parser/parser.py,sha256=XmAr0gRQeq6yGCW2AuKpyG6zfXMTOKXeN8g66L3gu-0,21035
|
|
18
|
+
hocon/_internal/properties/__init__.py,sha256=R1P782ZXwOlTd1kBrarEPxuAVKq23TZOHyRNOnPSw00,182
|
|
19
|
+
hocon/_internal/properties/properties.py,sha256=uBZKWgqkWYuOlHy1X54w7NYLek0r14HH4Ii8gKFA_9k,2688
|
|
20
|
+
hocon/_internal/resolver/__init__.py,sha256=JTeAQMuppzj0hIrNqcB2i8fPQWXXPR3UeH_yWSTaDzc,73
|
|
21
|
+
hocon/_internal/resolver/fold_self_ref.py,sha256=cdPk3qB_qay5OuCiChBAS8ns_t5VrgHdfMm1lZTuX-I,10025
|
|
22
|
+
hocon/_internal/resolver/include_loader.py,sha256=5ipTH1M_t9-IIfGR3kuHqOQvemGzCGfUROzvp4UMRGk,9089
|
|
23
|
+
hocon/_internal/resolver/resolver.py,sha256=0VJYD5g4a0S4Hz4RtZgcRD2drxjBWamzsh3LoYnuGaY,3131
|
|
24
|
+
hocon/_internal/resolver/structure_builder.py,sha256=FSbgQEwxiYC1T2RBjeZRos_lWmvoweCuDq1klRbWj7c,7073
|
|
25
|
+
hocon/_internal/resolver/substitution_resolver.py,sha256=ek08ORKPhyLpddE3dqwUbEgGn-NJ1hSFWxI4kPVeaOA,17797
|
|
26
|
+
hocon/_internal/resolver/types.py,sha256=n1IQIsmoF51fBcbLQ59dIT5EL7_9hVwSZko2umZHIW8,5353
|
|
27
|
+
hocon/_internal/resolver/utils.py,sha256=q29MD71cIINaw2MUq5ykooCVaGLlMZyeC6x54MFTtXo,4785
|
|
28
|
+
hocon_parser-1.8.0.dist-info/METADATA,sha256=U1DjDVy20UbWvHQLgxQj_HtU6nS6CWM78CbDx80-g18,18708
|
|
29
|
+
hocon_parser-1.8.0.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
30
|
+
hocon_parser-1.8.0.dist-info/licenses/LICENSE,sha256=1bIpOkF4bIM2xllNHV8QNTNB2yS32yV9pjXZIg6H4mg,11343
|
|
31
|
+
hocon_parser-1.8.0.dist-info/RECORD,,
|