Packages: Getting Started¶
Everything arvel ships — mail, cache, queues — is built on the same extension
points your package gets. This page takes you from an empty directory to a
published package that any arvel app can uv add and use with zero wiring:
installing it auto-registers your service provider through the
arvel.providers entry point.
The skeleton is maximal-subtractive: every contribution type a package can make is already there — the working ones as small examples, the rest as commented verbs — and you delete what you don't need. An un-pruned skeleton boots and passes its own CI before you touch a single file, so you always start from green.
1. Generate¶
arvel new payments --package
cd payments && uv sync
uv run pytest # green before you change anything
You get a complete package: pyproject.toml (entry point + extras +
strict-mypy + import-linter), a provider with every integration verb, a
Manager/driver pair with a fake driver, a facade, an example route and
console command, a test suite with a booted-app fixture, and CI + PyPI-release
workflows.
2. Prune¶
Open README.md in the generated package — it carries the keep/delete table.
Rule of thumb: keep pyproject.toml, provider.py, config.py, tests/, and
CI; delete any of manager/drivers, facade, routes, or commands your package
doesn't need. Deleting a file and its provider line is the whole operation.
3. Build¶
Replace the example driver with your real one. The two disciplines that keep a package healthy:
- Depend on arvel's shape, not its guts. Import from
arvel.contractsand the public API only — then a framework refactor can't break you. (Package Development covers why.) - Heavy engines stay off the import path. Lazy-import your engine inside
the driver method that uses it, give it an extra in
pyproject.toml, and list it in the import-linter contract. A missing engine then fails withuv add 'arvel-payments[stripe]'— the fix in the message — because your Manager setsextra_package = "arvel-payments".
Test against a bare Application() — the generated tests/conftest.py fixture
registers and boots your provider in-process, no host app or pip install
needed. Your users test their code against your package the same way they
test mail: Payments.fake() swaps in your fake driver and they assert on calls.
4. Wire more contributions¶
provider.py documents every verb inline; uncomment to activate:
| Verb | Contributes |
|---|---|
merge_config_from |
config defaults (host app values win) |
load_routes_from |
HTTP routes (see the example routes.py) |
load_migrations_from |
migrations run with the app's own |
load_views_from / load_translations_from |
namespaced views / translations |
commands(...) |
console commands |
publishes(...) |
files the app copies out via arvel vendor:publish |
provides() |
marks the provider deferred — zero boot cost until first use |
5. Publish¶
Tag v0.1.0 and push — release.yml builds and publishes to PyPI via trusted
publishing (configure the publisher once on pypi.org, no token secrets). Any
arvel app is now one uv add arvel-payments away from your provider
registering itself.
Common mistakes & gotchas¶
- Forgetting the entry point — a renamed provider class must be updated in
pyproject.tomlunderarvel.providers, or nothing registers (silently). - Route files wire in
register(), notboot()— the app loads route files right after provider registration; the async boot loop runs later, so aboot()-timeload_routes_fromsilently misses the window. And because route files load by path, use absolute imports inside them. - A top-level heavy import — breaks the host app's startup guarantee; the generated import-linter contract fails your CI first. Keep engines inside driver methods.
- Testing through your own wiring instead of the provider — the fixture boots the provider exactly as a host app would; if a binding only works when your test constructs it by hand, real apps are broken.
See also¶
- Package Development — the extras model, entry-point
discovery, and why
import arvelstays light. - Service Providers — every integration verb in depth.