netconf-parser


Namenetconf-parser JSON
Version 1.1.0 PyPI version JSON
download
home_pageNone
SummaryA Python library for parsing and comparing hierarchical network device configurations
upload_time2025-10-19 10:55:15
maintainerNone
docs_urlNone
authorNone
requires_python>=3.12
licenseMIT
keywords network configuration parser netconf cisco juniper diff compare
VCS
bugtrack_url
requirements pytest
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # 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"
}
        
Elapsed time: 1.79116s