NEON for Python
✅ A NEON parser that keeps every comment and space
✅ Edit a configuration file and the diff is only your change
✅ neon-lint for editors, CI and AI agents
✅ No dependencies, fully typed, Python 3.13+
NEON parser, encoder and lossless editor for Python. NEON is the human-friendly
configuration format of the Nette framework, a YAML alternative with entities, and the format of
phpstan.neon. This library parses it into a concrete syntax tree in which every character of
the file has its place, so printing the tree gives the file back byte for byte:
doc = parse(text)
doc.print() == text # True, for any input NEON accepts
That is what makes editing safe. Change a value, add a key, remove an item, and the rest of the file stays as its author wrote it:
doc.set_value(["database", "host"], "db.example.com")
- host: localhost # the server
+ host: db.example.com # the server
On that you build scripts that bump a setting in every phpstan.neon of a monorepo, installers
that register an extension in config.neon, migrations that rename a service, and tools that
read the configuration of a PHP project without running PHP.
The library comes from David Grudl, the author of Nette, Latte and Tracy. NEON has been the configuration format of Nette since 2011, read by every Nette application, and this implementation follows the same specification as nette/neon for PHP and @nette/neon for JavaScript.
NEON in one minute
# mappings and sequences, indented by tabs or spaces
database:
host: localhost
port: 3306
roles: [guest, member, admin] # inline arrays
# entities: a value with attributes, the way Nette writes services
mailer: App\Mailer(@smtp, timeout: 30)
note: '''
multiline strings
without escaping
'''
JSON is a subset of NEON, except for a colon at the start of a line glued to its value
("a" + new line + :1), which NEON reads as a literal. The format is described at
ne-on.org and in the manual.
▶ Runnable examples: examples/edge-cases
Format-preserving editing
Most YAML libraries for Python, like PyYAML, drop comments and formatting when they write a file. Here keeping them is an invariant, not an effort: every edit changes the tokens it edits, and the four guarantees of the specification (the file reads back as the same tree, with the expected value, with the change only where it was made) are checked after every operation of a large corpus of edits.
from neon import CommentPolicy, parse
doc = parse(text)
doc.set_value(["database", "port"], 3307) # 0x0CEA stays hexadecimal: 0x0CEB
doc.set_value(["database", "debug"], False) # yes becomes no, not false
doc.set_value(["timeout"], 30) # the same value: no change at all
doc.set_value(["parameters", "admin"], "a@b.cz") # a new key, indented like its siblings
doc.add_item(["services"], "App\\OrderFacade") # a new item of a list
doc.remove_item(["database", "driver"]) # with the comment that describes it
doc.remove_item(["mail"], comments=CommentPolicy.MOVE_TO_NEXT_TOKEN)
A missing key in the middle of a path is an error, so a typo does not create a new section.
Removing the last item of a collection leaves it empty (services: []), because in a
configuration empty and missing often mean different things; an item marked nullable loses its
value instead. A refused edit changes nothing at all.
▶ Runnable examples: examples/editing, examples/config-editor
Installation
uv add nette-neon # or: pip install nette-neon
The package is imported as neon. Python 3.13 or newer, fully typed (py.typed), no
dependencies.
The tree: nodes, slots, tokens, trivia
item = doc.find_item(["database", "user"])
item.get_value() # 'root'
item.get_trailing_comments() # the comment on its line
item.value.get_first_token().text # the text exactly as written
item.get_position() # Position(line, column, offset), from the current tree
Eight node classes (DocumentNode, BlockArrayNode, InlineArrayNode, ArrayItemNode,
EntityNode, EntityChainNode, StringNode, LiteralNode) with named slots, tokens with their
trivia (whitespace, line endings, comments), find(), find_ancestor(), siblings, and
Traverser for a walk with replacements.
▶ Runnable examples: examples/tree
Decoding and encoding
from neon import decode, decode_file, encode
decode("a: [1, 2]") # {'a': [1, 2]}
decode_file("phpstan.neon") # the value of a file
encode({"a": [1, 2]}) # '{a: [1, 2]}'
encode({"a": [1, 2]}, True) # 'a:\n\t- 1\n\t- 2\n\n'
encode({"a": [1, 2]}, lambda _, path: not path) # 'a: [1, 2]\n', one level of blocks
encode(config, lambda value, _: not isinstance(value, list)) # lists on one line
| NEON | Python |
|---|---|
null, true, yes ... |
None, bool |
| integer | int, of any size |
| float | float |
| string | str |
| date | Date (components and offset as written; to_datetime() for a datetime) |
| entity, chain | Entity |
| list | list |
| map | dict with string keys, in the order of the file |
The mapping is one-to-one: 1.0 stays a float, a big integer stays exact, {} is a dict and
[] a list, and a key keeps its place. encode() takes datetime and date as well.
decode() is typed as json.loads() is, Any; to_value() of the tree returns the exact
union neon.Value.
▶ Runnable examples: examples/parsing, examples/encoding
One format, one specification
The format, the value model, the tree and the semantics of editing are written down in a language-neutral specification with a conformance corpus, and every implementation runs that corpus in its tests. The values and the error messages were checked against nette/neon for PHP over 30,000 real-world files and 100,000 generated ones. The corpus is public; if you write an implementation of NEON for another language, use it.
For editors and AI agents
neon-lint config/
config/local.neon:3:2: Duplicated key 'user'
One line per finding, the exit code as the verdict, --json for tools; the same command, output
and exit codes as neon-lint of the other implementations. Without installing anything:
uvx --from nette-neon neon-lint config/.
For AI agents, run it after every edit of a .neon file and hand a finding back as an error,
so the agent fixes the file before it goes on. See docs/skills/agents.md.
▶ Runnable examples: examples/linting
Limits
- No formatter: the library keeps your formatting, it does not impose one.
- No schema or validation of values.
- No error recovery: an invalid file is reported, not partially parsed.
- Positions are computed on request, from the current tree.
Documentation and credits
The examples are the documentation of the API, each chapter with the real output of its programs. The format itself is documented in the Nette manual.
Released under the MIT license.
Metadata
Release files for nette-neon 1.0.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 | Provenance | Actions |
|---|---|---|---|---|
| nette_neon-1.0.0.tar.gz | 90.0 kB | Without provenance | Details for nette_neon-1.0.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| nette_neon-1.0.0-py3-none-any.whl | Python 3 | none | any | Details for nette_neon-1.0.0-py3-none-any.whl |
Total release size: 147.1 kB
Release files / nette_neon-1.0.0.tar.gz
| Download URL | nette_neon-1.0.0.tar.gz |
|---|---|
| Size | 90.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
51fd628fbcded370347ade9bc8c7642d4936c620b9be1b5a4cd2180f611bc4d7
|
|
BLAKE2b-256 checksum How to use checksums |
b03dcced2b486b125ecb994e2a8adbd3900020dcdb6f8e8345f2ddb91c6c75c5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / nette_neon-1.0.0-py3-none-any.whl
| Download URL | nette_neon-1.0.0-py3-none-any.whl |
|---|---|
| Size | 57.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
620bf34d1edb4c6685be8564c495b5364577ac45fb398de1a18633dbb57f280d
|
|
BLAKE2b-256 checksum How to use checksums |
e18cbe9546914771a9dae73534cf47f054ce08ef782c49ae2077c3d4a7c57a82
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for nette-neon 1.0.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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
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.