Round-trip parser and editor for Hyprland configuration files
Project description
hyprland-config
Round-trip parser and editor for Hyprland configuration files.
Quick start
from hyprland_config import load
config = load()
config.set("general:gaps_in", 20)
config.save()
That's it. load() reads ~/.config/hypr/hyprland.conf, follows all source directives, and builds a navigable document tree. set() finds the option in whichever sourced file defines it and updates it in place. save() writes only the files that were actually modified.
Installation
pip install hyprland-config
Requires Python 3.12+. Zero runtime dependencies.
Why this library
This is a round-trip parser. It keeps comments, blank lines, variable definitions, and formatting intact — editing one option doesn't rewrite the rest of the file.
It follows source directives across multiple files, resolves globs (including absolute paths for NixOS/home-manager setups), detects cycles, and only writes back files that actually changed. Writes are atomic (temp file + fsync + rename) so a crash mid-save won't corrupt your config.
300+ tests, including property-based and fuzz testing with Hypothesis.
Usage
Edit config options
from hyprland_config import load
config = load()
# Update existing options (finds them across all sourced files)
config.set("general:gaps_in", 10)
config.set("decoration:rounding", 8)
config.set("decoration:blur:enabled", True)
# Remove an option
config.remove("misc:vfr")
# Add a keybind (appends after existing binds)
config.append("bind", "SUPER, T, exec, kitty")
# Remove a specific keybind
config.remove_where("bind", lambda v: "killactive" in v)
# Remove an animation by name
config.remove_where("animation", lambda v: v.startswith("windows,"))
# Check which files have pending changes
config.dirty_files()
# [PosixPath('/home/user/.config/hypr/hyprland.conf.d/02_general.conf'),
# PosixPath('/home/user/.config/hypr/hyprland.conf.d/03_decoration.conf')]
# Save only the files that changed
config.save()
Read config as a flat dict
from hyprland_config import parse_to_dict
options = parse_to_dict("~/.config/hypr/hyprland.conf")
# Unique keys are strings
print(options["general:gaps_in"]) # "5"
# Repeated keys become lists
print(options["bind"]) # ["SUPER, Q, killactive,", "SUPER, Return, exec, kitty", ...]
Read option values
from hyprland_config import load
config = load()
# Get a value (returns string or None)
gaps = config.get("general:gaps_in") # "5"
missing = config.get("nonexistent", "default") # "default"
# Get all values for a repeated key
all_binds = config.get_all("bind") # ["SUPER, Q, killactive,", ...]
# Get the full node for more details
node = config.find("general:gaps_in")
print(f"{node.full_key} = {node.value} (line {node.lineno})")
# Find all binds as nodes
binds = config.find_all("bind")
# Expand variables
print(config.expand("$mainMod + Q")) # "SUPER + Q"
# Navigate sourced files
from hyprland_config import Source
for line in config.lines:
if isinstance(line, Source):
for sub_doc in line.documents:
print(f"{sub_doc.path.name}: {len(sub_doc.lines)} lines")
Parse from a string
from hyprland_config import parse_string
doc = parse_string("""
general {
gaps_in = 5
gaps_out = 10
}
bind = SUPER, Q, killactive,
""")
print(doc.get("general:gaps_in")) # "5"
Lenient mode
By default, the parser raises ParseError on malformed input. In lenient mode, unparseable lines are preserved as error nodes instead, so you can work with partially valid configs:
config = load(lenient=True)
# Inspect any lines that couldn't be parsed
for err in config.errors:
print(f"{err.source_name}:{err.lineno}: {err.raw}")
Check for deprecations
Track Hyprland deprecations across versions and apply automatic migrations:
from hyprland_config import load, check_deprecated, migrate
config = load()
# Check for deprecated options (covers v0.33–v0.53+)
warnings = check_deprecated(config)
for w in warnings:
print(f"{w.key}: {w.message} (deprecated in v{w.version_deprecated})")
# Auto-migrate what can be migrated
result = migrate(config)
print(f"Applied {len(result.applied)} migrations")
config.save()
Features
- Nested
category { }blocks, includingdevice[name] { } - Inline category syntax (
general:gaps_in = 5) - One-line blocks (
general { gaps_in = 5 }) source = pathfollowing with glob and~expansion, cycle detection$variabledefinitions and expansion- Expression evaluation (
{{2 + 2}}) with\{{escape support - Conditional directives (
# hyprlang if/elif/else/endif) and# hyprlang noerror - Comments, inline comments,
##escape, blank lines - Special keywords: bind (all flag variants), monitor, animation, bezier, env, exec, workspace, windowrule, and more
- Comment-preserving round-trip editing
- Lenient parsing mode for malformed or partial configs
- Deprecation checking and automatic migration (v0.33–v0.53+)
- Section listing and iteration
- Dirty tracking — only modified files are written to disk
- Atomic writes (temp file + fsync + rename)
ParseErrorwith file name and line number on malformed input- Fully typed with
py.typedmarker
License
MIT
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hyprland_config-0.1.0.tar.gz.
File metadata
- Download URL: hyprland_config-0.1.0.tar.gz
- Upload date:
- Size: 41.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b5c6558d1e829ce7d8c41effbc77628f478e7ad34793f6e504b286e06f32a127
|
|
| MD5 |
c8cf04b424e3e8e75cd567d5fc1d5f9a
|
|
| BLAKE2b-256 |
c1831353955170fbb5d3061552856b6a88f74da91ddeb9115aee26a31f3764ea
|
Provenance
The following attestation bundles were made for hyprland_config-0.1.0.tar.gz:
Publisher:
publish.yml on BlueManCZ/hyprland-config
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hyprland_config-0.1.0.tar.gz -
Subject digest:
b5c6558d1e829ce7d8c41effbc77628f478e7ad34793f6e504b286e06f32a127 - Sigstore transparency entry: 1154340353
- Sigstore integration time:
-
Permalink:
BlueManCZ/hyprland-config@73a0df131c485343680f8e03641f8b69d9e7a7a8 -
Branch / Tag:
- Owner: https://github.com/BlueManCZ
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@73a0df131c485343680f8e03641f8b69d9e7a7a8 -
Trigger Event:
release
-
Statement type:
File details
Details for the file hyprland_config-0.1.0-py3-none-any.whl.
File metadata
- Download URL: hyprland_config-0.1.0-py3-none-any.whl
- Upload date:
- Size: 24.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7c1c299bb30edb0d80cb39b0d55a0a5f2b712cc23f9679054d516a95687c3b9e
|
|
| MD5 |
2958810313adf119de06ef78bf92f84a
|
|
| BLAKE2b-256 |
b239d56ab230ac12a64855adab3c94b0f8ddc9c46b6a4eab1187a23fb18a1803
|
Provenance
The following attestation bundles were made for hyprland_config-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on BlueManCZ/hyprland-config
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hyprland_config-0.1.0-py3-none-any.whl -
Subject digest:
7c1c299bb30edb0d80cb39b0d55a0a5f2b712cc23f9679054d516a95687c3b9e - Sigstore transparency entry: 1154340356
- Sigstore integration time:
-
Permalink:
BlueManCZ/hyprland-config@73a0df131c485343680f8e03641f8b69d9e7a7a8 -
Branch / Tag:
- Owner: https://github.com/BlueManCZ
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@73a0df131c485343680f8e03641f8b69d9e7a7a8 -
Trigger Event:
release
-
Statement type: