- The Routing Intent by Leonardo Furtado
- Posts
- Chapter 25: Managing Python Projects with Poetry — Part 1
Chapter 25: Managing Python Projects with Poetry — Part 1
Bring consistency, clarity, and operational safety to your network automation code.

In case you're trying to read this through your email, be warned: This article is large and may get clipped by services like Gmail and others.
This isn't just an article or a typical post: it is a Python crash course delivered to your inbox. However, since services like Gmail often clip larger emails, I strongly recommend opening it in your browser instead.
Also, make sure to read the previous chapters before this one!
1. Why Poetry, and Why Now (for Network Engineers)
Network automation doesn’t move as one thing. It moves as a noisy convoy of tools and vendor SDKs, each on its own release cadence and with its own opinion about which Python and which transitive dependencies it will tolerate. Your everyday kit, like Ansible for change execution, Nornir for inventory-driven workflows, Scrapli and Netmiko for device sessions, ncclient for NETCONF/YANG operations, plus a sprinkling of vendor SDKs, rarely lines up on the same versions at the same time. That’s the normal state of play, not an exception. And yet most engineers still try to keep this convoy together with a hand-rolled requirements.txt and a silent hope that pip won’t surprise them during a change window.
Pip is an installer, not a contract. It will dutifully fetch “the latest compatible things” every time you run pip install -r requirements.txt, but “compatible” is evaluated on the fly from a sprawling dependency graph that changes underneath you. Last week’s “compatible” is not this week’s. A teammate running the same command on a jump host may end up with a slightly different set of transitive packages than you had in the lab. CI may resolve a different build than staging. Air-gapped recovery drills expose the worst of it: with no lockfile, you don’t actually know which exact wheel versions your automation depended on when it worked. You shipped intent with your code, but left the dependencies to probability.
This is where Poetry earns its keep, especially for network engineers who care about repeatable outcomes. Poetry turns dependency management from guesswork into policy. It gives you a single, auditable source of truth; pyproject.toml describes what you intend to use, and poetry.lock captures exactly what you did use when the system last resolved successfully. That lockfile is the difference between “works on my laptop” and “works everywhere we claim it does”: your lab VM, the shared bastion in production, and the CI runner that gates merges.
Poetry also understands that different parts of your network estate need different stacks, and it lets you express that cleanly. Your Junos/YANG work might live behind ncclient and lxml, while your Cisco playbooks rely on Scrapli/Netmiko, your test harness depends on pytest, pytest-asyncio, and rich for operator-friendly output, which you do not want on the production jump host. With Poetry, you don’t fork repos or maintain brittle comment blocks in a monolithic requirements.txt; you define dependency groups and optional extras that mirror reality. When you install for the lab, you include the lab groups. When you install on the bastion, you bring only the runtime set. When CI builds, it installs exactly what the lockfile says; no more, no less.
Critically, Poetry doesn’t replace the interpreter discipline you established with Pyenv; it sits above it. Pyenv pins which Python (like 3.8 for the production Ansible estate, 3.11 for your NETCONF pipelines, 3.12 for experimental Nornir tasks), while Poetry pins which packages for each of those worlds and records the precise resolution. That two-layer contract (interpreter + locked dependencies) is the operational equivalent of a well-designed underlay with a clear overlay: you trust it because it’s explicit, versioned, and reproducible.
Consider the lifecycle this enables. You set up a new repo for a maintenance window automation: pyenv local 3.9.x to match the approved runtime, poetry init to declare your intent, poetry add to pull in only what you need. Poetry resolves a coherent set of versions and freezes them in the lockfile. You push to Git; your teammate clones it on a different machine and runs poetry install, and they get the exact same environment, byte-for-byte. CI runs the test suite against the same lock; later, you update a single package and run poetry lock to re-resolve intentionally, review the changes in a merge request, and roll that change like any other network policy update. When the platform team asks for a software bill of materials or you practice an air-gapped restore, you can export the environment deterministically and even pre-cache the wheels. Nothing is left to “latest.”
In short, Poetry introduces the three traits network engineers insist on everywhere else: intent, isolation, and reproducibility. It gives automation the same rigor you apply to routing policies and change control. Pip alone will always be a moving target; requirements.txt will always age faster than you can babysit it.
Poetry is how you stop negotiating with transitive dependency drift and start operating your automation like a system, so the lab, the jump host, and CI/CD all see the same thing, every time. And this is what this article will discuss in the sections below.