Chapter 27: Managing Python Projects with Poetry — Part 3

Elevate your network automation development environment by adopting these software-focused cultural practices.

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!

7) Private Sources and Internal Packages: Shipping Your SD-WAN SDK

In most networks of consequence, you’ll consume private artifacts (vendor SDK wheels, internal helpers) and you’ll eventually ship your own (an SD-WAN controller client, inventory library, or a CLI your Ops team runs). Poetry makes both sides predictable, as long as you wire sources, credentials, and promotion flows deliberately.

The pattern to aim for:

  1. Declare sources in your project with safe priorities.

  2. Keep credentials out of Git; pass them via environment or Poetry’s keychain.

  3. Consume vendor/internal packages explicitly from the right index.

  4. Build signed artifacts for your code and publish them via CI under change control.

  5. Promote versions through gates (dev → staging → prod) in time with change windows.

  6. Retain artifacts and lockfiles for audits and disaster recovery.

A. Add private sources (indexes) with safe priorities

You’ll typically have:

  • PyPI for public packages.

  • One or more internal registries (Artifactory/Nexus/Cloudsmith) for vendor wheels and your org’s packages.

  • Sometimes, a separate staging vs prod registry.

Add sources with Poetry and mark internal ones as explicit so resolution never “accidentally” falls back to them.

# Add internal prod index and mark it explicit:
poetry source add --priority explicit corp-prod https://artifactory.example.com/api/pypi/pypi/simple

# Optional: a staging index for pre-release testing:
poetry source add --priority explicit corp-staging https://artifactory.example.com/api/pypi/staging/simple

This writes entries under [[tool.poetry.source]] in pyproject.toml. Example:

[[tool.poetry.source]]
name = "pypi"
url = "https://pypi.org/simple"
default = true

[[tool.poetry.source]]
name = "corp-prod"
url = "https://artifactory.example.com/api/pypi/pypi/simple"
priority = "explicit"

[[tool.poetry.source]]
name = "corp-staging"
url = "https://artifactory.example.com/api/pypi/staging/simple"
priority = "explicit"

Why explicit? Poetry will only use that source when you explicitly target it for a dependency. That prevents a misconfigured registry from shadowing PyPI.

B. Keep credentials out of Git (env or Poetry config)

Use Poetry’s HTTP Basic auth, supplied at runtime via environment or a secure CI secret. Two easy options:

1) Environment variables (preferred in CI):

export POETRY_HTTP_BASIC_CORP_PROD_USERNAME=$CI_USER
export POETRY_HTTP_BASIC_CORP_PROD_PASSWORD=$CI_TOKEN

The env var names follow POETRY_HTTP_BASIC_<UPPERCASE_SOURCE_NAME>_USERNAME|PASSWORD.

2) Poetry config on a developer machine (not committed):

poetry config http-basic.corp-prod "$USER" "$TOKEN"
# For staging too, if needed:
poetry config http-basic.corp-staging "$USER" "$TOKEN"

Don’t put creds in pyproject.toml. Don’t commit .pypirc with tokens. Use short-lived, scoped service-account tokens (read for install jobs; publish for release jobs) and rotate them like device credentials.

C. Consume vendor wheels and internal packages explicitly

Point particular dependencies at the right source. This keeps your graph predictable and auditable.

[tool.poetry.dependencies]
python = "^3.11"

# public packages
httpx = "^0.27"
pydantic = "^2.8"

# internal SD-WAN SDK (from corp-prod index)
company-sdwan-sdk = { version = "^1.8", source = "corp-prod" }

# vendor wheel delivered via corp-prod mirror
vendor-sdwan-api = { version = "~=3.2.0", source = "corp-prod" }

Now Poetry will fetch those two only from corp-prod. If you want to test a release candidate first, temporarily point at staging:

company-sdwan-sdk = { version = "1.9.0-rc.3", source = "corp-staging" }

Once validated, switch the version (and source) in a PR, re-lock, and roll during the approved change window.

D. Build your own artifacts (wheel + sdist)

When your automation grows beyond a one-off script, package it. Poetry bakes the wheel and source tarball:

poetry build
# Produces dist/yourpkg-1.4.0-py3-none-any.whl and dist/yourpkg-1.4.0.tar.gz

Adopt semantic versioning:

  • PATCH: bugfixes, no API changes (1.4.1)

  • MINOR: backward-compatible features (1.5.0)

  • MAJOR: breaking changes (2.0.0)

  • Use pre-release tags (alpha/beta/rc) for gated testing: 1.6.0-rc.2

Set the version with:

poetry version patch|minor|major|<explicit>

E. Publish to your registry (manual and CI)

Manual (developer machine with publish rights):

# Publish to prod registry
poetry publish -r corp-prod

# Or to staging
poetry publish -r corp-staging

CI (GitHub Actions example) on a release tag:

name: release
on:
  push:
    tags: ['v*.*.*', 'v*.*.*-rc.*']
jobs:
  build-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version-file: .python-version
      - uses: snok/install-poetry@v1
        with:
          virtualenvs-create: true
          virtualenvs-in-project: true
          installer-parallel: true
      - run: poetry version $(echo "${GITHUB_REF_NAME}" | sed 's/^v//')
      - run: poetry build
      - name: Publish to staging for RCs, prod for finals
        env:
          POETRY_HTTP_BASIC_CORP_STAGING_USERNAME: ${{ secrets.STAGING_USER }}
          POETRY_HTTP_BASIC_CORP_STAGING_PASSWORD: ${{ secrets.STAGING_TOKEN }}
          POETRY_HTTP_BASIC_CORP_PROD_USERNAME:    ${{ secrets.PROD_USER }}
          POETRY_HTTP_BASIC_CORP_PROD_PASSWORD:    ${{ secrets.PROD_TOKEN }}
        run: |
          if [[ "$GITHUB_REF_NAME" == *"-rc."* ]]; then
            poetry publish -r corp-staging
          else
            poetry publish -r corp-prod
          fi

This implements version gates: RCs land in staging; finals land in prod. Promotion is a merge/tag action, tied to a change window.

F. Promotion flows that match change control

Treat package promotion like you treat route-policy promotion:

  1. Develop in feature branches; publish -alpha/-beta artifacts to staging.

  2. Integrate and test in CI (unit + integration + device emulators).

  3. Validate in a lab or canary environment during a staging window.

  4. Promote by cutting a final tag (e.g., v1.8.0) that publishes to prod index.

  5. Roll to production bastions using locked installs (see Section 8).

  6. Retain artifacts + lockfiles for audit/rollback.

Your poetry.lock at each consuming repo records exactly what was used when the change succeeded. Roll back by reverting the lockfile and redeploying—no guesswork.

G. Exporting for pip-only hosts and air-gapped installs

Many control planes (AWX/Ansible Controller, some bastions) are still pip-native. Use Poetry to export the resolved graph and pre-download wheels:

# On a build box with access to both PyPI and corp-prod:
poetry export -f requirements.txt --with-hashes -o requirements.lock.txt
pip download -r requirements.lock.txt -d wheelhouse/ \
  --extra-index-url https://artifactory.example.com/api/pypi/pypi/simple

# Move wheelhouse/ and requirements.lock.txt to the target host (offline OK)
python -m venv .venv && source .venv/bin/activate
pip install --no-index --find-links wheelhouse/ --require-hashes -r requirements.lock.txt

This enforces the same resolved versions you tested, even without Poetry present.

H. Security and supply-chain hardening (practical minimums)

  • Explicit sources for internal deps; don’t let resolution wander.

  • Hash-pinned exports (--with-hashes) for pip-only installs.

  • Read vs publish tokens with least privilege; short TTLs; rotate routinely.

  • SBOM: keep poetry.lock and built artifacts alongside change records.

  • Quarantine new vendor wheels in staging; scan before promotion.

  • Repro caches: retain wheels for each approved build to survive upstream yanks.

I. Practical troubleshooting

  • “Poetry won’t use my internal index.”
    Ensure the dependency specifies source = "corp-prod" and that the source name matches exactly. With priority = "explicit", Poetry will ignore the index unless you target it.

  • “Credentials work locally but fail in CI.”
    CI env var names must be uppercase source names:
    POETRY_HTTP_BASIC_CORP_PROD_USERNAME|PASSWORD. Confirm the job actually needs publish rights (most jobs should be read-only).

  • “Vendor wheel is Linux-only; devs on macOS fail.”
    Provide a dev-only shim group (e.g., mock clients) or containerized dev workflows. For cross-platform libraries, ask vendors for universal wheels or source dists.

  • “Accidental dependency drift on bastions.”
    Never run poetry update there. Install from a known lock (export + wheelhouse) created by CI.

J. The enterprise payoff

With declared sources, clean credentials, and a gated publish flow, your SD-WAN SDK and related automation become real, first-class artifacts. Teams consume them with a single line, CI tests them under the same constraints, and change windows become about intended updates, not surprises from the public internet. Poetry gives you the levers; your process turns them into repeatable, auditable releases.

Subscribe to keep reading

This content is free, but you must be subscribed to The Routing Intent by Leonardo Furtado to continue reading.

Already a subscriber?Sign in.Not now