# ncclient wraps NETCONF in a Python API, and the device handler is where the real work is

> ncclient is the Apache-2.0 licensed Python library for scripting NETCONF against network devices, installed from PyPI and configured with a manager connection plus a device handler for Juniper, Cisco and other vendors. It is a library rather than a tool, so all of the surrounding workflow is yours to build.

**ncclient/ncclient** — Python library for NETCONF clients

- Repository: https://github.com/ncclient/ncclient
- Stars: 745 · Forks: 415
- Language: Python
- License: Apache-2.0
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/ncclient-ncclient

## NETCONF from Python, with the RPC plumbing already written

NETCONF is the XML-based management protocol used across network equipment, and it is one of those protocols where a first implementation is a week of work and a maintained library saves you that week. ncclient is that library. The README describes it as a Python library that facilitates client-side scripting and application development around the NETCONF protocol, and the repository description is shorter still, a Python library for NETCONF clients.

The intended users are network engineers writing automation, not operators looking for a command line tool. There is no ncclient binary in the documentation and no configuration file to edit. You install a package from PyPI, import a manager module, and open a connection inside a context manager. Everything else about your workflow, scheduling, logging, error handling and reporting, stays in your code.

That shape has an upside and a cost. The upside is that the library stays out of your way and plays well with whatever orchestration you already run, including a test suite and a CI job. The cost is that there is no built-in retry policy, no configuration schema, no dry-run mode and no inventory. If you are migrating from a full network automation platform with a GUI and job scheduler, ncclient is the engine, not the vehicle.

The project was developed by Shikar Bhushan and is now maintained by Leonidas Poulopoulos and Einar Nilsen-Nygaard. The documentation lives on Read the Docs, the package on PyPI, and the licence is Apache-2.0.

## Device handlers decide how ncclient talks to your platform

The interesting design decision in ncclient is the device handler. Since version 0.4.1 the library integrates Juniper's and Cisco's forks of NETCONF, and the README says a lot of new concepts have been introduced that ease management of those devices respectively, with the biggest change being the introduction of device handlers in the connection parameters.

A handler is a name passed at connect time, and it selects the behaviour differences for a vendor: which operations are valid, how the replies are shaped, what the lock semantics mean. The list the README gives is long enough to matter. Alcatel Lucent is alu, Ciena is ciena, Cisco splits into csr, nexus, iosxr and iosxe, H3C is h3c, HP Comware is hpcomware, Huawei has huawei and huaweiyang, Juniper is junos, and anything not in the list is served by the default handler.

The README's own framing is that device handlers are easy to implement and prove to be futureproof, and the repository layout backs that up: the examples directory is split into a base set and a vendor set, which is how you would expect a library to separate protocol level examples from platform specific ones.

The practical consequence for a new user is that picking the wrong handler is not a cosmetic mistake. Default against a Juniper box and you are sending Cisco shaped assumptions to a Junos device, and the resulting errors point at the network rather than at your handler choice.

## Installing from PyPI, and the optional ssh-python transport

The installation section is short. You install the package with pip, and there is an optional extra for people who want a different SSH transport:

```bash
    pip install ncclient

    # to install dependencies to use ssh-python instead of Paramiko
    pip install ncclient[libssh]
```

The extra exists because, as of 0.7.0, it is possible to use ssh-python instead of Paramiko, and the repository carries an example at examples/base/nc11.py that shows how. If you have a reason to prefer ssh-python, the extra pulls in the right dependency rather than leaving you to resolve it yourself.

Working from a clone is the same one-liner, the README gives pip install -U . for a local checkout. That matters more than it looks for contributors, because the same command is step two of the documented test setup.

Two system libraries are needed underneath. The requirements list names libxml2 and libxslt, and on Debian or Ubuntu the README points to installing libxml2-dev and libxslt1-dev through apt-get. On macOS or Windows the equivalent C libraries are a known friction point for anything built on lxml, and the README does not cover those platforms.

The quickest way to see the library in motion is to run the bundled examples, which the README does with a glob over the base examples directory.

## A first session: connect on port 830 and pull the running config

The usage section opens with a snippet that is close to the smallest useful thing you can do with the library. It connects to a host on the NETCONF port, fetches the running configuration, and writes it to a file named after the host:

```python
    from ncclient import manager

    with manager.connect(host=host, port=830, username=user, hostkey_verify=False) as m:
        c = m.get_config(source='running').data_xml
        with open("%s.xml" % host, 'w') as f:
            f.write(c)
```

The `with` block is not decoration. The manager connection is a context manager, and the pattern of opening it, doing work and letting it close is what the documentation shows everywhere. The `source='running'` argument selects the running datastore rather than the candidate, and `data_xml` gives you the reply as XML rather than a parsed object, which is the useful starting point when you are exploring an unfamiliar device.

Adding a handler is a single extra parameter. The README shows the Juniper version of the same code, which adds device_params to the connect call:

```python
    with manager.connect(host=host, port=830,
                         username=user, hostkey_verify=False,
                         device_params={'name':'junos'}) as m:
        c = m.get_config(source='running').data_xml
```

One thing to notice in both samples is `hostkey_verify=False`, which disables SSH host key checking. It is convenient in a lab and wrong in production, because it removes the protection against connecting to the wrong host. The examples are teaching examples, not a security model, and copying that flag into a production script is the single most likely way to end up with an ncclient integration that nobody wants to audit.

## The README requirements list has drifted from pyproject.toml

The two sources of dependency truth in this repository disagree, and the difference matters if you are pinning an environment.

The README requirements section lists Python 2.7 or Python 3.5+, setuptools 0.6+, Paramiko 1.7+, lxml 3.3.0+, libxml2 and libxslt. The pyproject.toml in the repository says something quite different. The build system requires hatchling 1.27 or newer with hatch-vcs 0.4 or newer, and the project metadata sets requires-python to 3.7 or later, with two runtime dependencies:

```toml
requires-python = ">=3.7"
dependencies = [
  "paramiko>=3.2.0",
  "lxml>=3.3.0",
]
```

So the Paramiko floor moved from 1.7 to 3.2.0, and the Python floor moved from 3.5 (or even 2.7) to 3.7. The classifiers list Python 3.7 through 3.13 and mark the project as Development Status 5, Production/Stable. The optional extras are declared too, with libssh mapping to ssh-python 1.1.1 or newer, plus test, docs and release groups carrying pytest, sphinx and twine.

The reason to care is that pip resolves what the metadata says, not what the README says. If you provision an old environment with Paramiko 1.x because the README told you to, the failure appears at install time or at first SSH use, not when you read the docs. Trust pyproject.toml and requirements.txt, which both name paramiko>=3.2.0 and lxml>=3.3.0.

## Releases are tag driven and GitHub Actions publishes to PyPI

The release process is documented well enough to run yourself, which tells you how little magic is involved. Builds use hatchling and version from Git tags using hatch-vcs, and the tag pattern in pyproject.toml is anchored so that only v-prefixed tags count:

```toml
[tool.hatch.version]
source = "vcs"
tag-pattern = "^v(?P<version>.+)$"
```

The tag must use the vX.Y.Z format, for example v0.7.1, and the documented sequence is to ensure the tests pass and the docs and changelog are updated, then create and push the tag:

```bash
       git tag v0.7.1
       git push origin v0.7.1
```

Two optional preflight steps follow. Distributions can be built locally with hatchling build, and the artefacts can be validated with python -m twine check dist/*. Publishing itself is not manual: GitHub Actions handles PyPI publishing on pushed v* tags. The version is derived from the tag rather than typed into a file, so the tag is the single source of truth and a mistyped tag becomes a wrong version on PyPI.

The release table in the README also records why some versions never made it to users. Version 0.6.8 was pulled due to an accidental breaking API change, and 0.6.5 was pulled because of a bug in the PyPI upload. Both entries are still there years later, which is useful history for anyone wondering why a version number is missing from the index.

Version 0.6.0 is worth a separate look for anyone upgrading from the 0.5 line. The table describes it as a minor release reinstating Python 3.7 and greater compatibility, but necessitating a change to client code that uses `async_mode`. The 0.5.4 entry explains the cause, the use of a new Python 3.7 keyword in function signatures, which had made 0.5.4 incompatible with Python 3.7.

## Running the test suite the way CI does

The developer section documents a local test loop in four steps, using virtualenvwrapper for the environment:

```bash
       mkvirtualenv ncclient-testing
```

Then the local package is installed into that environment with pip install -U ., the test dependencies are added with pip install pytest coverage coveralls mock, and the suite runs with pytest test --verbosity=3. For a coverage report that shows missing lines, the documented command wraps the test run:

```bash
       coverage run -m pytest test --verbosity=3 && coverage report -m
```

The repository layout supports this workflow. Alongside the test directory there is a tox.ini, a requirements-test.txt and a requirements-build.txt, and the coverage status is tracked through Coveralls while the CI workflow itself is driven from a .github directory. The docs are built separately with Sphinx, since the docs extra pulls in sphinx and the readthedocs configuration lives in a .readthedocs.yaml file.

Two housekeeping files are worth knowing about if you are contributing. SECURITY.md sits at the top level, so there is a defined route for reporting vulnerabilities rather than a public issue, and there is a NOTICE file alongside the LICENSE. The examples directory holds a vendor subdirectory as well as base, which is where you would add a new handler example.

## Where ncclient is the wrong choice

Three cases stand out.

The first is operational tooling. If the job is to pull a config once, by hand, from a device in a lab, a library that requires you to write Python is overhead. ncclient has no command line entry point in its documentation, so every ad-hoc use is a script you write and maintain yourself.

The second is the handler coverage gap. The handler list is long, but it is a list of named vendor behaviours, and anything outside it falls to the default handler, which the README positions as the option for a server or anything not in the above. That is a reasonable default and a real limit for a device whose NETCONF dialect you have not encountered. The alternative approach in that situation is to write the RPC handling yourself against lxml and skip the abstraction entirely, which costs you the handler work but removes the guesswork about how ncclient will shape the exchange.

The third is the SSH transport and its floor. The Paramiko requirement moved to 3.2.0, which rules out older environments, and the ssh-python option exists but is an extra you have to install and configure. If you are on a platform where neither transport installs cleanly, that is a decision made before you write a line of NETCONF code.

Finally, on maintenance. The repository is not archived and the last push was on 2026-08-20, with v0.7.1 released on 2026-03-15 and v0.7.0 on 2025-08-25. That is a maintained project, and it also means a pin to a specific version is a normal choice rather than a sign of distrust.

## Conclusion

Adopt ncclient if your automation talks NETCONF and you want a maintained Python API rather than your own XML layer, and pick the device handler that matches your platform before writing any calls. Do not adopt it if you need a ready made command line tool, a streaming subscription model, or support for protocols it does not implement. Before upgrading in a pinned environment, read the README requirements list against pyproject.toml, since they disagree about the Python floor and the Paramiko version, and check the release tag you pin because 0.6.8 and 0.6.5 were both pulled from PyPI after publication.

## FAQ

### How do I install ncclient for NETCONF scripting?

Install it from PyPI with pip install ncclient, and add the optional extra with pip install ncclient[libssh] if you want to use ssh-python instead of Paramiko. From a local clone the README gives pip install -U ., and libxml2 and libxslt need to be present as system libraries.

### What are ncclient device handlers and how do I use one?

Device handlers adapt the library to a vendor's flavour of NETCONF and are passed in the connection parameters, for example device_params={'name':'junos'} for Juniper. The README lists handlers for Alcatel Lucent, Ciena, Cisco, H3C, HP Comware, Huawei and Juniper, with default for anything else.

### How do I fetch a device running configuration with ncclient?

Open a manager connection inside a with block, for example manager.connect with host, port 830, username and hostkey_verify, then call m.get_config(source='running').data_xml. The returned XML can be written straight to a file.

### Which Python and Paramiko versions does ncclient require?

The pyproject.toml sets requires-python to 3.7 or later, with dependencies on paramiko>=3.2.0 and lxml>=3.3.0, and the classifiers list Python 3.7 through 3.13. The README requirements section is out of date, listing Python 2.7 or 3.5+ and Paramiko 1.7+.

### How are ncclient releases published to PyPI?

Versions come from Git tags using hatch-vcs with a vX.Y.Z tag pattern, and GitHub Actions handles PyPI publishing on pushed v* tags. Before tagging you should make sure the tests pass and the docs and changelog are updated; hatchling build and python -m twine check dist/* are available as local preflight steps.

## Sources

- [Issues](https://github.com/ncclient/ncclient/issues)
- [License: Apache-2.0](https://github.com/ncclient/ncclient/blob/master/LICENSE)
- [ncclient/ncclient on GitHub](https://github.com/ncclient/ncclient)
- [README](https://github.com/ncclient/ncclient/blob/master/README.md)
- [Releases](https://github.com/ncclient/ncclient/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/ncclient-ncclient
