Namespace packages¶
canVODpy uses Python 3.3+ implicit namespace packages to let eleven independent packages share the canvod.* import prefix: a unified API backed by separately installable wheels.
from canvod.readers import Rnxv3Obs
from canvod.auxiliary import Sp3File
from canvod.grids import EqualAreaBuilder
from canvod.store import MyIcechunkStore
How it works¶
A namespace package is created by omitting __init__.py from the shared parent directory. Python treats a directory without __init__.py as a namespace that multiple packages can contribute to.
canvod-readers/src/
canvod/ ← namespace directory, NO __init__.py
readers/
__init__.py ← regular package starts here
base.py
canvod-auxiliary/src/
canvod/ ← same namespace, different package
auxiliary/
__init__.py
src/
canvod/
__init__.py ← Makes canvod a REGULAR package
readers/ ← Blocks other packages from contributing
If any installed package adds canvod/__init__.py, it claims
exclusive ownership of canvod, breaking all other sub-packages.
After uv sync installs all packages:
site-packages/
canvod/ ← Python merges all contributors here
readers/ ← from canvod-readers
auxiliary/ ← from canvod-auxiliary
grids/ ← from canvod-grids
vod/ ← from canvod-vod
store/ ← from canvod-store
store_metadata/ ← from canvod-store-metadata
viz/ ← from canvod-viz
config/ ← from canvod-config
utils/ ← from canvod-utils
ops/ ← from canvod-ops
preflight/ ← from canvod-preflight
Build configuration¶
The uv_build backend uses a dotted module-name to produce the correct wheel structure:
# packages/canvod-readers/pyproject.toml
[build-system]
requires = ["uv_build>=0.13.0,<0.14.0"]
build-backend = "uv_build"
[tool.uv.build-backend]
module-name = "canvod.readers" # dot → namespace package
The dot in "canvod.readers" tells uv_build:
- Treat
canvod/as a namespace (do not include__init__.pyfor it). - Package only the
readers/subdirectory and its contents.
Wheel contents¶
canvod_readers-0.1.0-py3-none-any.whl
canvod/
readers/ ← package starts here (has __init__.py)
__init__.py
base.py
canvod_readers-0.1.0.dist-info/
METADATA
WHEEL
RECORD
Note
The wheel contains canvod/readers/ but no canvod/__init__.py.
This is correct: the absence of __init__.py is what makes it a namespace.
Import resolution¶
When Python processes from canvod.readers import Rnxv3Obs:
canvod: no__init__.pyfound \(\rightarrow\) namespace package (open for extension).readers: has__init__.py\(\rightarrow\) regular package (standard import).Rnxv3Obs: imported fromcanvod/readers/__init__.py.
Independent installation¶
Because each sub-package is self-contained, users install only what they need:
uv add canvod-readers # readers only
uv add canvod-readers canvod-store # subset
uv add canvodpy # everything
Verification¶
import canvod
print(canvod.__file__) # AttributeError: namespace packages have no __file__
from canvod import readers
print(readers.__file__) # e.g. …/site-packages/canvod/readers/__init__.py
Common pitfalls¶
Adding __init__.py to the namespace directory
src/canvod/__init__.py converts the namespace into a regular package,
preventing all other packages from contributing to canvod.*.
Incorrect module-name
# WRONG: creates a regular top-level package
module-name = "canvod_readers"
# CORRECT: creates a namespace sub-package
module-name = "canvod.readers"