What are Python namespace packages?
A namespace package lets separate installed packages share one import path. A user installs several distributions, but Python presents them as pieces of one package.
The common example is a company or platform namespace. One distribution might install acme.storage, another might install acme.billing, and application code imports both under the acme prefix. No single wheel owns all of acme.
Namespace packages are useful when that shared import path is the product. They are unnecessary when a normal package name or a plain prefix gives readers the same clarity with less coordination.
Split the install name from the import name
Python packaging has two names in play:
- The distribution name is what pip and uv use to install a project from an index such as PyPI.
- The import package name is what Python finds when code runs
import acme.storage.
Distribution packages and import packages do not have to match. Namespace packages add one more twist: several distribution packages can contribute to the same import package.
That gives this shape:
| Distribution package | Import path it contributes |
|---|---|
acme-storage |
acme.storage |
acme-billing |
acme.billing |
acme-identity |
acme.identity |
The user installs separate projects because each one has its own release cycle and dependencies. The user imports a shared acme namespace because the projects belong to one public family.
Let Python combine package portions
A regular package is a directory with an __init__.py file. When Python finds that file, that directory owns the package.
A native namespace package follows PEP 420’s implicit namespace package standard. The shared namespace directory omits __init__.py, so Python keeps looking across sys.path and combines every matching directory it finds.
Two separately installed distributions can therefore land in the same namespace:
-
- pyproject.toml
-
-
-
- __init__.py
- client.py
-
-
-
- pyproject.toml
-
-
-
- __init__.py
- invoices.py
-
-
Notice the missing src/acme/__init__.py file. That absence is the signal. The leaf packages, acme.storage and acme.billing, still have normal __init__.py files because they are regular packages inside the shared namespace.
With both distributions installed, application code can import from both portions, such as from acme.storage.client import StorageClient and from acme.billing.invoices import InvoiceClient.
If either distribution adds src/acme/__init__.py, it turns acme into a regular package and can hide the other distribution’s portion. That is the main operational hazard: every project sharing the namespace must follow the same rule.
Use namespaces for independently released families
Use a namespace package when the import path is a shared prefix for packages that ship separately.
Good fits include:
- An organization publishing client libraries under one brand, such as
acme.storageandacme.billing - Generated API clients where each service has separate dependencies and release timing
- Plugin namespaces where installed plugins should be discoverable under a shared package like
myapp.plugins.* - A uv workspace whose members need separate wheels but one import family after installation
The common thread is independent ownership. Each portion can be released, versioned, and installed without forcing users to install the whole family.
Prefer ordinary names for ordinary packages
Most projects should not use namespace packages. A normal import package is easier to explain and harder to break accidentally.
Prefer a regular package when:
- One distribution owns all of the import package
- The package needs initialization code in its top-level
__init__.py - Subpackages share tight internal implementation details
- Users install the whole library as one unit
- A flat prefix such as
acme_storageis clear enough
The prefix alternative avoids shared ownership. acme_storage and acme_billing look flatter than acme.storage, but each package has one owner and one directory. That trade is worth making unless the shared import namespace carries real product meaning.
Keep plugin systems explicit
Namespace packages are one way to discover plugins. If plugins install modules under myapp.plugins, the host application can scan that namespace and import the installed modules.
That works best when the filesystem location is the main contract: “put importable plugin modules here.” It works poorly when the plugin system needs metadata, disabled plugins, compatibility checks, or multiple entry points per distribution.
For those cases, package metadata entry points are clearer. A plugin declares a named entry under a group such as myapp.plugins, and the host reads that registry through importlib.metadata. The plugin does not need to live under the host application’s import package.
If a project uses namespace packages for plugins, keep the namespace narrow. Prefer myapp.plugins over making myapp itself a namespace package. A broken plugin should not make the host application’s main package unimportable.
Configure the build backend deliberately
Namespace packages are an import-system behavior, but the build backend still has to include the right files in the wheel.
For native namespace packages:
- Omit
__init__.pyonly from the shared namespace directory. - Keep
__init__.pyin the leaf package that the distribution owns. - Configure package discovery so the backend includes the leaf package.
- Document the namespace rule for every team that publishes into it.
With uv_build, a single namespace portion can use a dotted module name:
[tool.uv.build-backend]
module-name = "acme.storage"With setuptools, use namespace-package discovery rather than ordinary package discovery. The exact setting depends on whether the project uses pyproject.toml, setup.cfg, or setup.py, but the import layout rule stays the same: the shared directory has no __init__.py.
Avoid starting new projects with legacy pkgutil or pkg_resources namespace packages. They exist so older projects can keep working, not because they are a good default for new Python 3 packages. Migrating a legacy namespace requires coordinating every distribution that contributes to the namespace, so treat it as a planned packaging change, not a per-package cleanup.
Separate workspaces from namespaces
A monorepo workspace and a namespace package solve different problems.
A workspace is a repository-management tool. It lets several projects share a checkout, lockfile, virtual environment, or local dependency graph.
A namespace package is an import-path design. It decides what users type after installing one or more wheels.
Those choices often appear together, but neither one requires the other. A workspace can contain regular packages named storage_client and billing_client. Separate repositories can publish namespace portions named acme.storage and acme.billing. Use the workspace to coordinate development; use the namespace only if the installed import path should read as one family.
Choose the next step
- Building one library? Use a regular package and the src layout.
- Splitting a large repository into several projects? Start with uv workspaces and decide separately whether the installed packages need a shared import prefix.
- Publishing many related packages? Reserve the namespace and make every package follow the same native namespace-package pattern.
- Designing a plugin API? Prefer entry points unless import-path discovery is the main contract.