Adding a Python member package
Kolibri’s Python code is organized as a uv workspace. The main kolibri package is the workspace root; additional packages live under python_packages/.
Adding a new package
Create
python_packages/<package-name>/with its ownpyproject.toml— a normal, independently-buildable Python package (own[build-system], own staticversion, own dependencies).If it depends on
kolibriitself, resolve that dependency against this workspace instead of PyPI:[tool.uv.sources] kolibri = { workspace = true }
No change is needed to the root
pyproject.toml— its[tool.uv.workspace]memberslist already includes the globpython_packages/*, so any new package directory under it is picked up automatically.Run
uv sync --group devat the repo root to update the shared lockfile.Run
uv sync --group dev --all-packagesto install the new package into the shared workspace venv.--all-packagesis required — a plainuv sync(oruv sync --package <package-name>) scopes the sync to a single project and drops root Kolibri’s own runtime dependencies (Django, Click, etc.) from the shared venv.Add a test job for it in
.github/workflows/tox.yml, in the Stage 2 section (see “CI cascade” below) — model it on thesync_extras_plugin_testsjob, and add the new job’s id tostage2_required_checks’sneeds:list in the same file. A job left out of that list can fail without blocking merge, since branch protection only requiresstage2_required_checksitself to pass.Add the package’s import name to
known-first-partyin rootpyproject.toml’s[tool.ruff.lint.isort]table, so ruff sorts its own imports as first-party rather than third-party.
Member package versions are independent of each other and of the main kolibri package — there’s no enforcement linking them. Use a static version = "x.y.z" field, not setuptools-scm-derived dynamic versioning: this repo’s git tags are Kolibri’s own release tags, so dynamic versioning inside the workspace would report Kolibri’s version instead of the package’s own.
Marking a package as publishable
By default, a package under python_packages/ is workspace-only — nothing publishes it. To publish it to PyPI:
Add its
pyproject.tomlpath to thepaths:filter in.github/workflows/pypi_packages_publish.yml’spushtrigger.Add its name to the
workflow_dispatch.inputs.pypi_package.optionslist in the same file.Register a pending trusted publisher on PyPI and TestPyPI (see the “Python packages” section of the release process docs) before merging.
CI cascade
Python tests run in two stages (.github/workflows/tox.yml):
Stage 1 (blocking, fast feedback): core Kolibri tests on Python 3.10, and Postgres tests. Runs in parallel.
Stage 2 (gated on Stage 1 via
needs:): the rest of the Python version matrix, plus a test job per publishable member package. Acts as a broader safety net — it rarely fails once Stage 1 passes, but still gates merge.
Both stages are required checks in branch protection. Lint, wheel build, and JS tests are unaffected — they run in their own workflows, in parallel with Stage 1.