# Network Configuration Parser
A Python library for parsing and analyzing hierarchical network device configurations with space-based indentation.
## Features
- Parse network configurations from files or strings
- Build hierarchical parent-child relationships based on indentation
- Rich metadata for each configuration line (level, children, siblings, etc.)
- Search configurations with flexible filters
- Compare configurations to identify differences
- Support for any device with space-based indentation (Cisco, Arista, etc.)
## Installation
### From PyPI (Recommended)
```bash
pip install netconf-parser
```
### From Source
```bash
git clone https://github.com/w4hf/netconf-parser.git
cd netconf-parser
pip install -e .
```
### For Development
```bash
git clone https://github.com/w4hf/netconf-parser.git
cd netconf-parser
pip install -e ".[dev]"
```
## Quick Start
### Basic Usage
```python
from netconf_parser import Conf
# Load from a string
config_text = """hostname router1
interface GigabitEthernet0/1
ip address 192.168.1.1 255.255.255.0
no shutdown
description WAN Link
"""
conf = Conf.from_string(config_text)
# Or load from a file
conf = Conf.from_file("config.txt")
# Access all lines
print(f"Total lines: {len(conf.lines)}")
# Access root-level lines
for line in conf.root_lines:
print(f"Root line: {line}")
```
### Working with ConfLine Objects
Each line in the configuration is represented by a `ConfLine` object with rich metadata:
```python
# Get a line
line = conf.lines[1] # interface line
# Access properties
print(f"Content: {line.content}") # ['interface', 'GigabitEthernet0/1']
print(f"Line number: {line.line_num}")
print(f"Level: {line.level}")
print(f"Has children: {line.has_children}")
print(f"Has parent: {line.has_parent}")
print(f"Is lone line: {line.lone_line}")
# Access relationships
print(f"Direct children: {line.direct_children_count}")
print(f"All descendants: {line.all_children_count}")
# Iterate through children
for child in line.children:
print(f" Child: {child}")
# Check siblings
for sibling in line.siblings:
print(f" Sibling: {sibling}")
```
### Searching Configurations
Use `search_line_start_with()` to find specific lines:
```python
from netconf_parser import search_line_start_with
# Find all interface lines at level 0
interfaces = search_line_start_with(conf, "interface", level=0)
# Find IP address lines under interfaces
ip_addresses = search_line_start_with(
conf,
"ip address",
level=1,
parent_start_with="interface"
)
# Find descriptions under a specific interface
descriptions = search_line_start_with(
conf,
"description",
level=1,
parent_start_with="interface GigabitEthernet0/1"
)
```
### Comparing Configurations
Compare two configurations to identify differences:
```python
from netconf_parser import compare_confs
# Load two configurations
ref_conf = Conf.from_file("baseline.txt")
new_conf = Conf.from_file("current.txt")
# Compare them
deleted, added, modified_root, modified_children = compare_confs(
ref_conf,
new_conf,
ignore_regex=[r"^!", r"^#"] # Ignore lines starting with ! or #
)
# Check deleted lines
print("Deleted lines:")
for line in deleted:
print(f" - {line}")
# Check added lines
print("Added lines:")
for line in added:
print(f" + {line}")
# Check modified root lines
print("Modified root lines:")
for line in modified_root:
print(f" ~ {line}")
# Check modified children
print("Modified children:")
for group in modified_children:
print(f" Group with parent: {group[0].parent}")
for line in group:
print(f" ~ {line}")
```
## API Reference
### Classes
#### `Conf`
Represents a complete network configuration.
**Class Methods:**
- `from_string(config_text: str) -> Conf`: Create from a multiline string
- `from_file(file_path: str) -> Conf`: Create from a file
**Properties:**
- `lines`: List of all ConfLine objects
- `root_lines`: List of root-level ConfLine objects (level 0)
#### `ConfLine`
Represents a single configuration line with hierarchical properties.
**Properties:**
- `content`: List of keywords (whitespace removed)
- `line_num`: Line number in the configuration
- `level`: Indentation depth (0 = root)
- `has_children`: Boolean indicating if line has children
- `has_parent`: Boolean indicating if line has a parent
- `parent`: Reference to parent ConfLine (or None)
- `children`: List of direct child ConfLine objects
- `siblings`: List of sibling ConfLine objects
- `lone_line`: True if line has no parent or children
- `direct_children_count`: Number of direct children
- `all_children_count`: Total number of descendants
- `direct_children`: List of direct children's content
- `all_children`: List of all descendants' content
### Functions
#### `search_line_start_with()`
Search for configuration lines matching specific criteria.
```python
search_line_start_with(
conf: Conf,
search_string: str,
level: int,
parent_start_with: str | None = None
) -> list[ConfLine]
```
**Parameters:**
- `conf`: The Conf object to search
- `search_string`: String that lines must start with
- `level`: The indentation level to match
- `parent_start_with`: Optional filter for parent line content
**Returns:** List of matching ConfLine objects
#### `compare_confs()`
Compare two configurations and identify differences.
```python
compare_confs(
reference_conf: Conf,
compared_conf: Conf,
ignore_regex: list[str] = None
) -> tuple[list[ConfLine], list[ConfLine], list[ConfLine], list[list[ConfLine]]]
```
**Parameters:**
- `reference_conf`: The baseline configuration
- `compared_conf`: The configuration to compare
- `ignore_regex`: List of regex patterns for lines to ignore. Lines starting with any of these patterns will be excluded from comparison
**Returns:** A tuple containing:
1. `deleted_lines`: Lines in reference but not in compared
2. `added_lines`: Lines in compared but not in reference
3. `modified_root_lines`: Root lines with same start but different content
4. `modified_children`: Groups of child lines with modifications
## Running Tests
```bash
pytest tests/
```
To run with verbose output:
```bash
pytest -v tests/
```
To run a specific test file:
```bash
pytest tests/test_parser.py
```
## Requirements
- Python 3.12+
- pytest 8.0+ (for testing)
## License
This project is provided as-is for parsing network device configurations.
## Examples
### Example 1: Analyzing Interface Configuration
```python
from netconf_parser import Conf, search_line_start_with
config = """
interface GigabitEthernet0/1
description WAN Link
ip address 192.168.1.1 255.255.255.0
no shutdown
interface GigabitEthernet0/2
description LAN Link
ip address 10.0.0.1 255.255.255.0
shutdown
"""
conf = Conf.from_string(config)
# Find all interfaces with "shutdown" configured
for interface in conf.root_lines:
if interface.content[0] == "interface":
has_no_shutdown = any(
" ".join(child.content) == "no shutdown"
for child in interface.children
)
has_shutdown = any(
" ".join(child.content) == "shutdown"
for child in interface.children
)
if has_shutdown and not has_no_shutdown:
print(f"Interface {interface.content[1]} is shutdown")
```
### Example 2: Configuration Audit
```python
from netconf_parser import Conf
config = """
interface GigabitEthernet0/1
ip address 192.168.1.1 255.255.255.0
interface GigabitEthernet0/2
ip address 10.0.0.1 255.255.255.0
description Configured
interface GigabitEthernet0/3
ip address 172.16.0.1 255.255.255.0
"""
conf = Conf.from_string(config)
# Find interfaces without descriptions
print("Interfaces without descriptions:")
for line in conf.root_lines:
if line.content[0] == "interface":
has_description = any(
child.content[0] == "description"
for child in line.children
)
if not has_description:
print(f" - {line.content[1]}")
```
### Example 3: Configuration Diff Report
```python
from netconf_parser import Conf, compare_confs
old_config = """hostname old-router
interface GigabitEthernet0/1
ip address 192.168.1.1 255.255.255.0"""
new_config = """hostname new-router
interface GigabitEthernet0/1
ip address 192.168.1.2 255.255.255.0"""
old_conf = Conf.from_string(old_config)
new_conf = Conf.from_string(new_config)
deleted, added, modified_root, modified_children = compare_confs(old_conf, new_conf)
print("Configuration Changes Report")
print("=" * 50)
print(f"Deleted lines: {len(deleted)}")
print(f"Added lines: {len(added)}")
print(f"Modified root lines: {len(modified_root)}")
print(f"Modified child groups: {len(modified_children)}")
```
Raw data
{
"_id": null,
"home_page": null,
"name": "netconf-parser",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.12",
"maintainer_email": "Hamza Bouabdallah <w4hf@pm.me>",
"keywords": "network, configuration, parser, netconf, cisco, juniper, diff, compare",
"author": null,
"author_email": "Hamza Bouabdallah <w4hf@pm.me>",
"download_url": "https://files.pythonhosted.org/packages/72/0b/74c8605973606472bf9527297100b9e70d5c7b55b86282c4f52070cc45b0/netconf_parser-1.1.0.tar.gz",
"platform": null,
"description": "# Network Configuration Parser\n\nA Python library for parsing and analyzing hierarchical network device configurations with space-based indentation.\n\n## Features\n\n- Parse network configurations from files or strings\n- Build hierarchical parent-child relationships based on indentation\n- Rich metadata for each configuration line (level, children, siblings, etc.)\n- Search configurations with flexible filters\n- Compare configurations to identify differences\n- Support for any device with space-based indentation (Cisco, Arista, etc.)\n\n## Installation\n\n### From PyPI (Recommended)\n\n```bash\npip install netconf-parser\n```\n\n### From Source\n\n```bash\ngit clone https://github.com/w4hf/netconf-parser.git\ncd netconf-parser\npip install -e .\n```\n\n### For Development\n\n```bash\ngit clone https://github.com/w4hf/netconf-parser.git\ncd netconf-parser\npip install -e \".[dev]\"\n```\n\n## Quick Start\n\n### Basic Usage\n\n```python\nfrom netconf_parser import Conf\n\n# Load from a string\nconfig_text = \"\"\"hostname router1\ninterface GigabitEthernet0/1\n ip address 192.168.1.1 255.255.255.0\n no shutdown\n description WAN Link\n\"\"\"\n\nconf = Conf.from_string(config_text)\n\n# Or load from a file\nconf = Conf.from_file(\"config.txt\")\n\n# Access all lines\nprint(f\"Total lines: {len(conf.lines)}\")\n\n# Access root-level lines\nfor line in conf.root_lines:\n print(f\"Root line: {line}\")\n```\n\n### Working with ConfLine Objects\n\nEach line in the configuration is represented by a `ConfLine` object with rich metadata:\n\n```python\n# Get a line\nline = conf.lines[1] # interface line\n\n# Access properties\nprint(f\"Content: {line.content}\") # ['interface', 'GigabitEthernet0/1']\nprint(f\"Line number: {line.line_num}\")\nprint(f\"Level: {line.level}\")\nprint(f\"Has children: {line.has_children}\")\nprint(f\"Has parent: {line.has_parent}\")\nprint(f\"Is lone line: {line.lone_line}\")\n\n# Access relationships\nprint(f\"Direct children: {line.direct_children_count}\")\nprint(f\"All descendants: {line.all_children_count}\")\n\n# Iterate through children\nfor child in line.children:\n print(f\" Child: {child}\")\n\n# Check siblings\nfor sibling in line.siblings:\n print(f\" Sibling: {sibling}\")\n```\n\n### Searching Configurations\n\nUse `search_line_start_with()` to find specific lines:\n\n```python\nfrom netconf_parser import search_line_start_with\n\n# Find all interface lines at level 0\ninterfaces = search_line_start_with(conf, \"interface\", level=0)\n\n# Find IP address lines under interfaces\nip_addresses = search_line_start_with(\n conf, \n \"ip address\", \n level=1, \n parent_start_with=\"interface\"\n)\n\n# Find descriptions under a specific interface\ndescriptions = search_line_start_with(\n conf,\n \"description\",\n level=1,\n parent_start_with=\"interface GigabitEthernet0/1\"\n)\n```\n\n### Comparing Configurations\n\nCompare two configurations to identify differences:\n\n```python\nfrom netconf_parser import compare_confs\n\n# Load two configurations\nref_conf = Conf.from_file(\"baseline.txt\")\nnew_conf = Conf.from_file(\"current.txt\")\n\n# Compare them\ndeleted, added, modified_root, modified_children = compare_confs(\n ref_conf, \n new_conf,\n ignore_regex=[r\"^!\", r\"^#\"] # Ignore lines starting with ! or #\n)\n\n# Check deleted lines\nprint(\"Deleted lines:\")\nfor line in deleted:\n print(f\" - {line}\")\n\n# Check added lines\nprint(\"Added lines:\")\nfor line in added:\n print(f\" + {line}\")\n\n# Check modified root lines\nprint(\"Modified root lines:\")\nfor line in modified_root:\n print(f\" ~ {line}\")\n\n# Check modified children\nprint(\"Modified children:\")\nfor group in modified_children:\n print(f\" Group with parent: {group[0].parent}\")\n for line in group:\n print(f\" ~ {line}\")\n```\n\n## API Reference\n\n### Classes\n\n#### `Conf`\n\nRepresents a complete network configuration.\n\n**Class Methods:**\n- `from_string(config_text: str) -> Conf`: Create from a multiline string\n- `from_file(file_path: str) -> Conf`: Create from a file\n\n**Properties:**\n- `lines`: List of all ConfLine objects\n- `root_lines`: List of root-level ConfLine objects (level 0)\n\n#### `ConfLine`\n\nRepresents a single configuration line with hierarchical properties.\n\n**Properties:**\n- `content`: List of keywords (whitespace removed)\n- `line_num`: Line number in the configuration\n- `level`: Indentation depth (0 = root)\n- `has_children`: Boolean indicating if line has children\n- `has_parent`: Boolean indicating if line has a parent\n- `parent`: Reference to parent ConfLine (or None)\n- `children`: List of direct child ConfLine objects\n- `siblings`: List of sibling ConfLine objects\n- `lone_line`: True if line has no parent or children\n- `direct_children_count`: Number of direct children\n- `all_children_count`: Total number of descendants\n- `direct_children`: List of direct children's content\n- `all_children`: List of all descendants' content\n\n### Functions\n\n#### `search_line_start_with()`\n\nSearch for configuration lines matching specific criteria.\n\n```python\nsearch_line_start_with(\n conf: Conf,\n search_string: str,\n level: int,\n parent_start_with: str | None = None\n) -> list[ConfLine]\n```\n\n**Parameters:**\n- `conf`: The Conf object to search\n- `search_string`: String that lines must start with\n- `level`: The indentation level to match\n- `parent_start_with`: Optional filter for parent line content\n\n**Returns:** List of matching ConfLine objects\n\n#### `compare_confs()`\n\nCompare two configurations and identify differences.\n\n```python\ncompare_confs(\n reference_conf: Conf,\n compared_conf: Conf,\n ignore_regex: list[str] = None\n) -> tuple[list[ConfLine], list[ConfLine], list[ConfLine], list[list[ConfLine]]]\n```\n\n**Parameters:**\n- `reference_conf`: The baseline configuration\n- `compared_conf`: The configuration to compare\n- `ignore_regex`: List of regex patterns for lines to ignore. Lines starting with any of these patterns will be excluded from comparison\n\n**Returns:** A tuple containing:\n1. `deleted_lines`: Lines in reference but not in compared\n2. `added_lines`: Lines in compared but not in reference\n3. `modified_root_lines`: Root lines with same start but different content\n4. `modified_children`: Groups of child lines with modifications\n\n## Running Tests\n\n```bash\npytest tests/\n```\n\nTo run with verbose output:\n\n```bash\npytest -v tests/\n```\n\nTo run a specific test file:\n\n```bash\npytest tests/test_parser.py\n```\n\n## Requirements\n\n- Python 3.12+\n- pytest 8.0+ (for testing)\n\n## License\n\nThis project is provided as-is for parsing network device configurations.\n\n## Examples\n\n### Example 1: Analyzing Interface Configuration\n\n```python\nfrom netconf_parser import Conf, search_line_start_with\n\nconfig = \"\"\"\ninterface GigabitEthernet0/1\n description WAN Link\n ip address 192.168.1.1 255.255.255.0\n no shutdown\ninterface GigabitEthernet0/2\n description LAN Link\n ip address 10.0.0.1 255.255.255.0\n shutdown\n\"\"\"\n\nconf = Conf.from_string(config)\n\n# Find all interfaces with \"shutdown\" configured\nfor interface in conf.root_lines:\n if interface.content[0] == \"interface\":\n has_no_shutdown = any(\n \" \".join(child.content) == \"no shutdown\" \n for child in interface.children\n )\n has_shutdown = any(\n \" \".join(child.content) == \"shutdown\" \n for child in interface.children\n )\n \n if has_shutdown and not has_no_shutdown:\n print(f\"Interface {interface.content[1]} is shutdown\")\n```\n\n### Example 2: Configuration Audit\n\n```python\nfrom netconf_parser import Conf\n\nconfig = \"\"\"\ninterface GigabitEthernet0/1\n ip address 192.168.1.1 255.255.255.0\ninterface GigabitEthernet0/2\n ip address 10.0.0.1 255.255.255.0\n description Configured\ninterface GigabitEthernet0/3\n ip address 172.16.0.1 255.255.255.0\n\"\"\"\n\nconf = Conf.from_string(config)\n\n# Find interfaces without descriptions\nprint(\"Interfaces without descriptions:\")\nfor line in conf.root_lines:\n if line.content[0] == \"interface\":\n has_description = any(\n child.content[0] == \"description\" \n for child in line.children\n )\n if not has_description:\n print(f\" - {line.content[1]}\")\n```\n\n### Example 3: Configuration Diff Report\n\n```python\nfrom netconf_parser import Conf, compare_confs\n\nold_config = \"\"\"hostname old-router\ninterface GigabitEthernet0/1\n ip address 192.168.1.1 255.255.255.0\"\"\"\n\nnew_config = \"\"\"hostname new-router\ninterface GigabitEthernet0/1\n ip address 192.168.1.2 255.255.255.0\"\"\"\n\nold_conf = Conf.from_string(old_config)\nnew_conf = Conf.from_string(new_config)\n\ndeleted, added, modified_root, modified_children = compare_confs(old_conf, new_conf)\n\nprint(\"Configuration Changes Report\")\nprint(\"=\" * 50)\nprint(f\"Deleted lines: {len(deleted)}\")\nprint(f\"Added lines: {len(added)}\")\nprint(f\"Modified root lines: {len(modified_root)}\")\nprint(f\"Modified child groups: {len(modified_children)}\")\n```\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "A Python library for parsing and comparing hierarchical network device configurations",
"version": "1.1.0",
"project_urls": {
"Changelog": "https://github.com/w4hf/netconf-parser/releases",
"Documentation": "https://github.com/w4hf/netconf-parser#readme",
"Homepage": "https://github.com/w4hf/netconf-parser",
"Issues": "https://github.com/w4hf/netconf-parser/issues",
"Repository": "https://github.com/w4hf/netconf-parser"
},
"split_keywords": [
"network",
" configuration",
" parser",
" netconf",
" cisco",
" juniper",
" diff",
" compare"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "4ef988777a2e991e216734c8f8152ccdbcee94d09a0234439b2d73c0e471b3b9",
"md5": "69cd2aa15e56699336c3874218ec2a12",
"sha256": "962839f0e7fc2c518c95877839eeffad1e302e6e31dbb90b9db6686d4a588007"
},
"downloads": -1,
"filename": "netconf_parser-1.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "69cd2aa15e56699336c3874218ec2a12",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.12",
"size": 10927,
"upload_time": "2025-10-19T10:55:14",
"upload_time_iso_8601": "2025-10-19T10:55:14.149763Z",
"url": "https://files.pythonhosted.org/packages/4e/f9/88777a2e991e216734c8f8152ccdbcee94d09a0234439b2d73c0e471b3b9/netconf_parser-1.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "720b74c8605973606472bf9527297100b9e70d5c7b55b86282c4f52070cc45b0",
"md5": "49991d699bbe21990ed0e76c60b69389",
"sha256": "75d833e1e1fa31dbd13aa9c4c1de8af787be7a5c5ead6656a6b53ec27562067d"
},
"downloads": -1,
"filename": "netconf_parser-1.1.0.tar.gz",
"has_sig": false,
"md5_digest": "49991d699bbe21990ed0e76c60b69389",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.12",
"size": 17388,
"upload_time": "2025-10-19T10:55:15",
"upload_time_iso_8601": "2025-10-19T10:55:15.408777Z",
"url": "https://files.pythonhosted.org/packages/72/0b/74c8605973606472bf9527297100b9e70d5c7b55b86282c4f52070cc45b0/netconf_parser-1.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-10-19 10:55:15",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "w4hf",
"github_project": "netconf-parser",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"requirements": [
{
"name": "pytest",
"specs": [
[
">=",
"8.0.0"
]
]
}
],
"lcname": "netconf-parser"
}