NEON for Python
The package nette-neon reads and writes NEON in Python, for example your config.neon or the
phpstan.neon of a PHP project. With it you:
- decode a configuration file into Python values and encode values back to NEON
- edit a file while every comment and space you did not touch stays where it was
- check files with
neon-lintin your editor, in CI or after an AI agent
Installation
pip install nette-neon
Or uv add nette-neon. The package is imported as neon. It requires Python 3.13 or newer, it is fully
typed and has no dependencies.
Decoding and Encoding
The function decode() turns NEON into Python values, decode_file() does the same for a file, and
encode() turns values back:
from neon import decode, decode_file, encode
decode("roles: [guest, admin]") # {'roles': ['guest', 'admin']}
config = decode_file("config.neon")
encode({"roles": ["guest", "admin"]}) # '{roles: [guest, admin]}'
encode({"roles": ["guest", "admin"]}, True) # 'roles:\n\t- guest\n\t- admin\n\n'
Instead of True, the second argument of encode() can be a function that decides for each array
whether to write it as a block. It gets the array and the list of keys on the way to it:
encode(config, lambda value, path: not path) # blocks for the first level only
The values of the format become these Python values:
| NEON | Python |
|---|---|
null, true, yes … |
None, bool |
| integer | int, of any size |
| float | float |
| string | str |
| date | neon.Date |
| entity, chain | neon.Entity |
| sequence | list |
| mapping | dict with string keys, in the order of the file |
The mapping is one-to-one: 1.0 stays a float, a large integer stays exact, {} is a
dict and [] a list. A neon.Date holds the date as it was written, including a
missing time zone; to_datetime() turns it into a datetime. When encoding, datetime and
date are accepted as well.
Errors
When the input is not valid NEON, decode() and parse() raise neon.NeonError. Its message
says where the problem is, and its attribute position holds the line and the column:
from neon import NeonError, decode
try:
decode("database:\n\thost: localhost\n\thost: db")
except NeonError as e:
print(e) # Duplicated key 'host' on line 3 at column 2
print(e.position.line) # 3
Editing Files
The function parse() gives the syntax tree of a file, and through it you change the file so that the rest of it
stays as its author wrote it:
from pathlib import Path
from neon import parse
file = Path("config.neon")
doc = parse(file.read_text(encoding="utf-8"))
doc.set_value(["database", "host"], "db.example.com")
file.write_text(doc.print(), encoding="utf-8")
How to change, add, remove and rename items, and how to find your way around the tree, is described on the page Editing Files.
Checking Files: neon-lint
The package comes with the command neon-lint. Give it files or directories, and it prints one line for each
problem:
neon-lint config/
config/local.neon:3:2: Duplicated key 'user'
The command, its options and its exit codes are the same as in the PHP library. To run it without installing the package, use
uvx --from nette-neon neon-lint config/.
An AI agent that runs it after every change of a .neon file learns about a broken file right away. The guide for AI agents shows how to set it up.
More examples, each with its real output, are in the repository of the library.