Chapter 09Lesson 01150–195 min

PyPI, Conda, Python Package Indexes, Proxy Behavior, Uploads, and Metadata Management: Concepts, Architecture, and Mental Model

Model Python package delivery through Nexus: projects, versions, wheels, source distributions, Simple API metadata, hashes, hosted/proxy/group routing, pip indexes, Twine publication, Conda channels, credentials, and dependency-confusion boundaries.

PyPISimple APIWheels & sdistspip indexesConda

Learning objectives

  • Separate a Python project name, release version, distribution file, Simple API record, and installed import package.
  • Explain how wheels and source distributions differ operationally and why their hashes are artifact evidence rather than trust proof.
  • Map pip and Twine requests onto Nexus PyPI hosted, proxy, and group repositories.
  • Explain why index-url and extra-index-url are security-relevant routing decisions.
  • Contrast PyPI indexes with Conda channels without pretending the two package ecosystems share the same metadata model.

Current baseline. This chapter pins self-hosted Nexus Repository Community Edition 3.95.2, released 2026-08-21. Current Nexus supports PyPI hosted, proxy, and group repositories. PEP 658 and PEP 691 support arrived in 3.93, PEP 700 Simple API v1.1 fields in 3.94, and the 3.95 line includes PyPI proxy/group correctness improvements. Conda proxy, hosted, and group repositories are also current; hosted/group support was introduced in 3.92.0.

1. The practical problem: “a Python package” is several different things

Chapter 08 used manifest digests to identify exact container content. Python packaging has a different vocabulary. A project such as learner-ch09-widget can publish version 0.1.0, and that version can have several distribution files: perhaps a source distribution such as learner_ch09_widget-0.1.0.tar.gz plus one or more wheel files. The installed import package may be named learner_ch09_widget, while the distribution project name shown to pip uses normalized package-name rules.

Nexus must preserve enough metadata for pip to discover candidate files, choose a compatible distribution, download bytes, and verify hashes. A repository operator therefore needs to distinguish project metadata, release metadata, file assets, proxy cache state, client cache state, credentials, and the package code that may execute during installation.

2. Project → version → distribution file

ObjectExampleOperational meaning
Projectlearner-ch09-widgetThe package-distribution identity queried from an index. Project-name normalization means punctuation/case variants can map to the same normalized name.
Version0.1.0A release identifier interpreted under Python version semantics such as PEP 440.
Wheellearner_ch09_widget-0.1.0-py3-none-any.whlA built distribution. Compatible wheels can usually be installed without running a source build.
sdistlearner_ch09_widget-0.1.0.tar.gzA source distribution. Installing it may invoke a build backend in an isolated build environment.
Simple API entry/simple/learner-ch09-widget/Index metadata listing candidate files and attributes/hashes.
Installed importimport learner_ch09_widgetRuntime Python module/package name; it is not automatically identical to the distribution project spelling.

3. The Simple Repository API is the client-facing index contract

pip does not need the Nexus administrative UI to resolve packages. It talks to a Python package index, most importantly the Simple Repository API. The root index lists projects and a project detail endpoint lists distribution files. Modern clients can negotiate HTML or JSON representations. Nexus currently supports the long-standing PEP 503 HTML model plus PEP 691 JSON; PEP 658/714 metadata attributes allow core metadata to be fetched without downloading an entire wheel, and PEP 700 adds fields such as file size and upload time to Simple API v1.1 JSON responses.

NX_URL="http://127.0.0.1:8081"
REPO="pypi-public-or-group"

curl -fsS "$NX_URL/repository/$REPO/simple/"
curl -fsS "$NX_URL/repository/$REPO/simple/packaging/"
curl -fsS   -H 'Accept: application/vnd.pypi.simple.v1+json'   "$NX_URL/repository/$REPO/simple/packaging/"

Those are package-protocol reads. They are different from the Nexus REST APIs under /service/rest/v1/, which expose repository-manager objects such as repositories, components, and assets.

4. Hashes bind a file link to bytes—but do not establish trust by themselves

A Simple API file link can carry a hash, normally SHA-256. pip can use hashes when configured for hash-checking mode, and modern JSON index responses include hash fields. That hash is valuable evidence: if the downloaded wheel changes by one byte, the SHA-256 changes. It does not prove who built the wheel, whether the source was reviewed, whether a dependency is vulnerable, or whether the publisher account was authorized. Chapter 23 will add SBOM, provenance, vulnerability, and policy layers to this artifact evidence.

5. Wheel and sdist have different execution surfaces

A compatible wheel is a built distribution. pip can normally install its files directly after compatibility and integrity checks. A source distribution is source input to a build. Modern pip usually creates an isolated build environment, resolves build-system requirements from pyproject.toml, invokes the selected backend, produces a wheel, and then installs that wheel. This means an sdist path can execute build tooling before the package is installed.

Supply-chain boundary: “the sdist hash matched” means the downloaded source archive matched the expected bytes. It does not mean the build backend or source code is safe to execute. Use disposable environments for demonstrations and prefer a compatible wheel when the lesson is about repository routing rather than source builds.

6. Hosted, proxy, and group have the same roles—but Python-specific protocol behavior

TypePython roleState changed
HostedAuthoritative destination for internal wheel/sdist publication. Twine uploads here.Repository/component metadata in the database plus uploaded distribution bytes in the selected blob store.
ProxyManaged cache in front of PyPI or another Python index.Remote metadata/file cache state plus database/blob records for fetched content.
GroupSingle read endpoint combining hosted, proxy, and other groups.Group configuration and derived/cached index behavior; the group is not the publication target in this course.
pip cacheClient-side HTTP/wheel cache outside Nexus.Local user/client state only; clearing it does not clear Nexus proxy state.

Current 3.95.x fixes matter here: the release line includes changes for cached PyPI JSON fallback, correct proxy ETag behavior, authoritative group filename entries, and repaired .whl.metadata content types. That is why a production diagnosis must record the actual Nexus patch level rather than say only “Nexus 3.”

7. index-url and extra-index-url are routing policy

--index-url replaces pip's default index with one chosen endpoint. --extra-index-url adds another index. pip's own documentation explicitly warns against using an extra index for private packages because pip does not assign simple priority to the main versus extra index; candidates from all configured locations are considered and the best version match can win. That can create dependency confusion if an attacker publishes the same project name publicly with a more attractive version.

Python package request path with one controlled Nexus endpoint
flowchart LR
  DEV[Developer or CI] -->|pip install| GROUP[Nexus PyPI group]
  GROUP -->|internal candidate| HOSTED[PyPI hosted]
  GROUP -->|public candidate| PROXY[PyPI proxy]
  PROXY -->|Simple API + files| PYPI[PyPI upstream]
  DEV --> CACHE[pip local cache]
  HOSTED --> DB[(Nexus database)]
  PROXY --> DB
  HOSTED --> BLOB[(Blob store)]
  PROXY --> BLOB

The safest client pattern for this course is one Nexus-controlled index endpoint. If the group contains public proxy content, Nexus remains the egress/routing boundary and can later apply routing rules, content selectors, Firewall policy, and other controls. A client configured with an extra direct PyPI URL can bypass those controls.

8. Consumption credentials and publication credentials are separate concerns

pip reads from an index; Twine uploads to a hosted repository. Nexus uses repository privileges to authorize those operations. Do not publish with an administrator account simply because it is convenient. A CI publisher should have only the hosted-repository write privileges it needs; a consumer generally needs read/browse privileges.

Sonatype documents pip.conf/pip.ini for pip and .pypirc for Twine. Those files can contain plaintext credentials. In this chapter, the mandatory lab isolates client configuration beneath a temporary directory, uses a permission-restricted netrc for pip/curl reads, passes Twine credentials through environment variables only for the upload process, and removes them afterward. Production should use your platform's secret store and HTTPS.

9. Conda is a channel ecosystem, not “PyPI with another extension”

Conda packages are organized by channels and platform subdirectories such as linux-64, osx-arm64, or noarch. Channel metadata such as repodata.json describes package files and dependency constraints. Current Nexus supports Conda proxy repositories and, since 3.92.0, hosted and group repositories. It accepts both .conda and .tar.bz2 package formats and supports Conda 4.6 and later.

Do not collapse PyPI and Conda metadata models
flowchart TB
  PY[Pip / PyPI client] --> SIMPLE[Simple API project index]
  SIMPLE --> WHL[Wheel or sdist files]
  CONDA[Conda client] --> CHANNEL[Channel + subdir]
  CHANNEL --> REPODATA[repodata.json metadata]
  REPODATA --> PKG[.conda or .tar.bz2 package]
  SIMPLE -. both can be governed by .-> NX[Nexus repository topology]
  CHANNEL -. both can be governed by .-> NX

The common Nexus concepts are repository type, blob/database state, authorization, proxy caching, and group routing. The package-protocol metadata is ecosystem-specific.

10. Read-only inspection before mutation

Before creating a repository or uploading a package, capture: Nexus version/edition/runtime, existing PyPI/Conda repository types, repository URLs, group order, proxy remote URL, deployment policy on hosted repositories, client index configuration, and current components/assets. For a current PyPI repository, probe both HTML and JSON Simple API representations. For Conda, inspect the configured repository types rather than assuming hosted/group support based on an old tutorial.

curl -fsS http://127.0.0.1:8081/service/rest/v1/status
python --version
python -m pip --version
# Do not run 'pip config debug' against a normal user profile in captured evidence;
# it may reveal credential-bearing configuration. The hands-on lesson creates
# a fully isolated pip configuration before inspecting effective routes.

# If a disposable Nexus account is required, use a temporary netrc file
# rather than embedding username:password into the URL or command history.

11. Common wrong mental models

  • “The project name is the import name.” Often related, not guaranteed identical.
  • “A wheel and sdist are interchangeable files.” They represent different build/install paths.
  • “extra-index-url is a fallback with lower priority.” pip explicitly does not guarantee that interpretation.
  • “A group owns the uploaded package.” Hosted owns publication; group is a read aggregation endpoint.
  • “The pip cache and Nexus proxy cache are the same thing.” They are independent client/server state.
  • “Conda hosted/group never exist in Nexus.” That was once an outdated assumption; current support changed in 3.92.
  • “A matching SHA-256 proves a safe package.” It proves byte identity, not publisher trust or vulnerability status.

12. Mini lab: inspect a public package without changing Nexus configuration

If the disposable instance already has a PyPI group or proxy, inspect packaging

LAB="${TMPDIR:-/tmp}/nexus-ch09-readonly"
rm -rf "$LAB" && mkdir -p "$LAB/cache"
export PIP_CACHE_DIR="$LAB/cache"
export INDEX_URL="http://127.0.0.1:8081/repository/pypi-group/simple"

python -m pip download --no-deps --only-binary=:all:   --index-url "$INDEX_URL" --trusted-host 127.0.0.1   packaging==25.0 -d "$LAB/download"

python - <<'PYI'
from pathlib import Path
import hashlib, os
for p in Path(os.environ["LAB"], "download").glob("*.whl"):
    print(p.name, hashlib.sha256(p.read_bytes()).hexdigest())
PYI

If your repository requires authentication, do not add credentials to the URL. Lesson 2 creates an isolated credential file safely.

Knowledge check

What is the difference between a Python project version and a wheel filename?

Why is extra-index-url risky for private package names?

Does a SHA-256 from the Simple API prove the package is non-malicious?

Why is a source distribution operationally different from a wheel?

What changed in Nexus for Conda in 3.92.0?

13. Summary and next step

Python package delivery is a chain from index metadata to candidate distribution files to local installation. Nexus controls the hosted/proxy/group boundary, but the package ecosystem defines how clients discover and interpret those files. Keep one controlled index route, distinguish wheel from sdist execution, treat hashes as identity evidence, isolate credentials, and verify actual format/version support.

Lesson 2 turns this model into a disposable end-to-end workflow with a real synthetic package, a fresh virtual environment, Twine publication, Simple API JSON/HTML inspection, and a Conda metadata fixture.

Official references and version notes

Version-sensitive statements were rechecked against Sonatype and Python Packaging primary documentation on 2026-08-26. The mandatory lab assumes self-hosted Nexus Repository Community Edition 3.95.2, the bundled/supported Java 21 runtime, a disposable single-node local instance, and a current Python 3 environment. Record python --version, python -m pip --version, python -m twine --version, and python -m build --version locally because Python packaging clients evolve independently of Nexus.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.