Skip to main content

pydhcpcdparser

CI PyPI release workflow Coverage PyPI version Python versions License

Parse, inspect, edit, validate, and render dhcpcd.conf files with lossless round-tripping.

pydhcpcdparser is a pure-Python library. It does not configure network interfaces or invoke dhcpcd.

Table of Contents

Features

  • Lossless round-trip — comments, blank lines, indentation, unknown directives, and duplicate entries are preserved byte-for-byte
  • Minimal-diff editing — only the changed directive is rewritten; everything else stays identical
  • Scope-aware queries — interface, ssid, and profile blocks follow upstream dhcpcd semantics
  • Unknown-directive preservation — forward-compatible with future dhcpcd options
  • Separate validation — parsing never fails on unknown content; validation reports diagnostics
  • Effective configuration — compute which directives apply for a given interface/ssid/profile
  • Zero runtime dependencies — pure Python, works on 3.9+
  • Fully typed — ships py.typed with complete type annotations

Installation

pip install pydhcpcdparser

Quick Start

Parse and Inspect

from pydhcpcdparser import parse

source = open("/etc/dhcpcd.conf").read()
doc = parse(source)

# Round-trip: unmodified render matches the original byte-for-byte
assert doc.render() == source

# Inspect interfaces
for name in doc.interfaces():
    print(name)

eth0 = doc.interface("eth0")
print(eth0.static("ip_address"))   # "192.168.1.17/32"
print(eth0.static("routers"))      # "192.168.1.1"

# Edit with minimal diff — only the changed line is rewritten
eth0.set_static("ip_address", "192.168.1.20/24")
doc.write_atomic("/etc/dhcpcd.conf")

Create From Scratch

from pydhcpcdparser import Document

doc = Document()
doc.add_directive("hostname")
doc.add_directive("clientid")
doc.add_blank()

eth0 = doc.add_interface("eth0")
eth0.add_directive("nogateway")
eth0.set_static("ip_address", "192.168.1.17/32")
eth0.set_static("routers", "192.168.1.1")
eth0.set_static("domain_name_servers", "8.8.8.8 8.8.4.4")

print(doc.render())

Major Functionality Blocks

  • Parsing with lossless AST and scope derivation:
    • Lexer following upstream get_line() semantics (comment detection, quoting, escaping).
    • Flat ordered AST with scope blocks derived from interface, ssid, profile directives.
  • Query and inspection:
    • Document-wide and scope-scoped find, find_all, static, static_all queries.
    • Interface/profile/ssid listing, physical blocks vs logical views.
  • Minimal-diff editing:
    • set, remove, replace_first, replace_all, set_static, remove_static.
    • Only modified nodes are rewritten; unmodified nodes preserve original raw text.
  • Validation and effective configuration:
    • Directive registry with ~90 known directives and argument requirements.
    • validate() returns diagnostics without modifying the document.
    • effective_config() approximates upstream read_config() directive selection.

Parsing and Rendering

Function Description
parse(text, strict=False) Parse text into a Document
render(doc) Render a Document back to text
doc.render() Method form of render(doc)

Scope Access

Method Description
doc.interface(name) Logical view combining all interface blocks for name
doc.interface_blocks(name) Each physical interface block separately
doc.profile(name) Logical view combining all profile blocks for name
doc.profile_blocks(name) Each physical profile block separately
doc.ssid(name) Logical view combining all ssid blocks for name
doc.ssid_blocks(name) Each physical ssid block separately
doc.global_scope() The global (leading) scope
doc.scopes() Every scope block as a ScopeView

Query API

Method Description
doc.find(name) First directive matching name, or None
doc.find_all(name) All directives matching name
doc.interfaces() Distinct interface names in file order
doc.profiles() Distinct profile names in file order
doc.ssids() Distinct ssid names in file order
scope.find(name) First directive in scope, or None
scope.find_all(name) All directives in scope
scope.static(key) Last static key=... value, or None
scope.static_all(key) All static key=... values

Editor API

Method Description
scope.set(name, value=None) Set or append a directive
scope.set_static(key, value) Set or append static key=value
scope.add_directive(name, value=None) Always append a new directive
scope.remove(name) Remove first occurrence
scope.remove_all(name) Remove all occurrences
scope.remove_static(key) Remove first static key=...
scope.remove_all_static(key) Remove all static key=...
scope.replace_first(name, value) Replace first match
scope.replace_all(name, value) Replace all matches
doc.add_directive(name, value=None) Append directive to document
doc.add_blank() Append blank line
doc.add_comment(text) Append comment line
doc.add_interface(name) Append interface block
doc.add_profile(name) Append profile block
doc.add_ssid(name) Append ssid block
doc.ensure_interface(name) Get or create interface scope
doc.ensure_profile(name) Get or create profile scope
doc.ensure_ssid(name) Get or create ssid scope

File I/O

Method Description
Document.from_path(path) Parse a document from a file
doc.write(path) Write the rendered document
doc.write_atomic(path) Atomic write (temp file + fsync + replace)

Validation

from pydhcpcdparser import parse, validate

doc = parse("interface\nfuture_thing value\nnogateway\nnogateway\n")
for d in validate(doc):
    print(d)
# error: line 1: "interface" requires a value
# warning: line 2: Unknown dhcpcd directive "future_thing"
# warning: line 4: Multiple values found for "nogateway" ...

Diagnostic codes: missing-value, unknown-directive, duplicate-singleton.

Effective Configuration

eff = doc.effective_config(interface="eth0")
eff = doc.effective_config(interface="eth0", ssid="Office WiFi")
eff = doc.effective_config(profile="static_eth0")

eff.find("metric")
eff.static("ip_address")
eff.static_all("routes")

Error Handling

from pydhcpcdparser import (
    parse,
    DhcpcdParseError,
    ScopeNotFoundError,
)

# Strict parsing
try:
    doc = parse('env val="unterminated', strict=True)
except DhcpcdParseError:
    print("Malformed syntax")

# Missing scope
try:
    eth0 = doc.interface("eth99")
except ScopeNotFoundError:
    print("Interface not found")

All library errors inherit from DhcpcdParserError:

  • DhcpcdParseError — malformed syntax in strict mode
  • DhcpcdValidationError — validation failures
  • ScopeNotFoundError — requested scope does not exist
  • AmbiguousScopeError — ambiguous scope reference

Examples

See EXAMPLES.md for:

  • parsing, inspection, and round-trip examples
  • minimal-diff editing workflows
  • creating configurations from scratch
  • profile fallback patterns
  • validation and effective configuration usage

API Reference

For the complete public surface inventory, see API.md:

  • all public functions
  • all public classes and methods
  • all AST node types
  • all error classes

Development

# Show available targets
make help

# Create local virtual environment and install dependencies
make venv

# Run tests in Docker
make test

# Lint and type-check in Docker
make lint
make typecheck

# Run all checks (lint, typecheck, docs, security, tests)
make check

# Format code
make format

# Remove local virtual environment
make venv-clean

Release

GitHub Actions workflow in .github/workflows/release.yml handles package publishing via PyPI trusted publisher.

One-time setup:

Release flow:

# Tag a release (must match version in pyproject.toml)
git tag v0.1.0
git push origin v0.1.0

The release workflow validates that the tag version matches the package version in pyproject.toml, builds distributions, creates a GitHub Release with auto-generated notes, and publishes to PyPI.

Upstream Reference

Parser behavior is verified against NetworkConfiguration/dhcpcd at commit 5a91691c65f063551553116ca73f2163ad951c9c.

See docs/upstream-compatibility.md for detailed behavioral notes.

License

MIT License - see LICENSE for details.

Metadata

Release files for pydhcpcdparser 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydhcpcdparser 0.1.0
File Size Uploaded
pydhcpcdparser-0.1.0.tar.gz 55.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pydhcpcdparser 0.1.0
File Interpreter ABI Platform
pydhcpcdparser-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 79.5 kB

Release files / pydhcpcdparser-0.1.0.tar.gz

Download URL pydhcpcdparser-0.1.0.tar.gz
Size 55.7 kB
Tags Source
SHA-256 checksum
How to use checksums
70b92f7176a36b2798e0cff1b5807ce6867e39e0d14b14075bd0b98763ade987
BLAKE2b-256 checksum
How to use checksums
391bb3793a0cb850ee57f097bfe6dd53467f1a3ad66e53d368fb46ff3ebaf4ac
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 4, 2026.

Transparency log

Release files / pydhcpcdparser-0.1.0-py3-none-any.whl

Download URL pydhcpcdparser-0.1.0-py3-none-any.whl
Size 23.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
49c57da3c236818f31f494946e2c57ecb8677134b5aedb423cf3ad5bdcafc38e
BLAKE2b-256 checksum
How to use checksums
36f5c0ef320f16c82f5c55b60f84a8554bdb2d35d1bc7ecf2878011b0ab63597
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 4, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page