Package Development
Introduction
Section titled “Introduction”Packages are how first-party and community code plugs into Almasix: a
ServiceProvider registers bindings, merges config, loads routes / views /
translations / migrations, offers publishable assets, and optionally registers
console commands. Scaffold a package inside your app (or a sibling repo),
install it editable, and publish its assets:
smith make:package courierpip install -e packages/couriersmith vendor:publish --tag=courier-config# resolve a namespaced view or hit a package route to confirm the provider bootedService providers
Section titled “Service providers”Every package ships a provider that subclasses
almasix.providers.ServiceProvider. Call the helpers from register() (config
and bindings) and boot() (routes, views, publishes, commands):
from pathlib import Pathfrom almasix.providers import ServiceProvider
_HERE = Path(__file__).resolve().parent
class CourierServiceProvider(ServiceProvider): def register(self) -> None: self.merge_config_from(_HERE / "config" / "courier.py", "courier")
def boot(self) -> None: self.publishes( {_HERE / "config" / "courier.py": self.app.path("config", "courier.py")}, "courier-config", ) self.load_routes_from(_HERE / "routes" / "web.py") self.load_views_from(_HERE / "resources" / "views", "courier") self.load_translations_from(_HERE / "lang", "courier") self.load_migrations_from(_HERE / "database" / "migrations") self.publishes_migrations( { _HERE / "database" / "migrations" / "create_courier_messages_table.py": ( "database/migrations/create_courier_messages_table.py" ), }, "courier-migrations", ) self.commands([CourierStatusCommand])Register the provider in the application’s config/app.py providers list,
or rely on auto-discovery via entry points.
Package discovery
Section titled “Package discovery”Installed packages advertise providers with the almasix.providers
entry-point group:
[project.entry-points."almasix.providers"]courier = "courier.provider:CourierServiceProvider"On boot, Application.register_configured_providers registers:
FoundationServiceProvider- Every path in
config/app.py→providers - Every discovered entry point unless discovery is disabled
Configuring discovery
Section titled “Configuring discovery”config = { "providers": [ "app.providers.app_service_provider.AppServiceProvider", ], # Skip entry-point discovery entirely. "skip_provider_discovery": False, # Distribution names to exclude from auto-discovery. "dont_discover": ["almasix-spam"],}PackageManifest resolves entry points through importlib.metadata. A broken
optional package is skipped softly so one bad install cannot take down boot.
Resources
Section titled “Resources”Configuration — merge_config_from
Section titled “Configuration — merge_config_from”Loads a Python config module (config = {...} or top-level names) under a key.
Package defaults fill missing keys; values already in the application win
(published config overrides package defaults):
self.merge_config_from(_HERE / "config" / "courier.py", "courier")# config("courier.driver")Routes — load_routes_from
Section titled “Routes — load_routes_from”Executes a route file through Application.load_route_file — the same path
broadcasting uses for routes/channels.py:
self.load_routes_from(_HERE / "routes" / "web.py")Views — load_views_from
Section titled “Views — load_views_from”Registers a Prism namespace so view("courier::welcome") resolves against the
package tree. Published overrides live under
resources/views/vendor/courier/ and win over the package hint. The helper
also declares a {namespace}-views publish tag for the whole views directory.
self.load_views_from(_HERE / "resources" / "views", "courier")from almasix.prism.helpers import renderrender("courier::welcome", {"driver": "pigeon"})Migrations — load_migrations_from / publishes_migrations
Section titled “Migrations — load_migrations_from / publishes_migrations”load_migrations_from registers directories with the migrator so
smith migrate finds package migrations without publishing them.
Package sources use slug-only names (create_widgets_table.py) — no
YYYY_MM_DD_HHMMSS_ prefix.
publishes_migrations is like publishes, but vendor:publish stamps each
destination as YYYY_MM_DD_HHMMSS_{slug}.py (incrementing the second when
several migrations publish together) so they sort after the application’s
existing migrations.
Translations — load_translations_from
Section titled “Translations — load_translations_from”Registers a language namespace. Catalog files live under
{path}/{locale}/{group}.py; keys use namespace::group.key:
self.load_translations_from(_HERE / "lang", "courier")__("courier::messages.greeting")Commands — commands
Section titled “Commands — commands”Registers Command subclasses on the console kernel when it is bound
(providers’ boot() runs after the kernel is available during Smith boots):
self.commands([CourierStatusCommand])Publishing assets
Section titled “Publishing assets”publishes / publishes_migrations only declare paths. Users copy them
with:
smith vendor:publish --tag=courier-configsmith vendor:publish --provider=courier.provider.CourierServiceProvidersmith vendor:publish --allConvention for tags: {package}-config, {package}-migrations,
{package}-views, {package}-lang. See Smith Console for the
full vendor:publish surface.
Naming
Section titled “Naming”| Kind | Convention |
|---|---|
| First-party Almasix packages | Python: almasix.* (Signet, Prism, …). JS clients: @almasix/* (Sonar). |
| Third-party / community | Own distribution name (almasix-courier, acme-billing); do not claim the almasix. import namespace. |
| Config / publish tags | Package short name: courier, courier-config. |
| View / lang namespaces | Same short name: courier::welcome, courier::messages.greeting. |
Scaffolding — smith make:package
Section titled “Scaffolding — smith make:package”smith make:package couriersmith make:package acme-billing --path=packages/billingCreates under packages/{name}/ (or --path):
pyproject.tomlwith analmasix.providersentry pointsrc/{module}/provider.pywith samplemerge_config_from/load_*/publishescalls- Config, routes, views, lang, and migration stubs
- A README with install and publish notes
Testing packages
Section titled “Testing packages”- Unit-test the provider by constructing it with an
Application(tmp_path), callingregister()/boot(), and asserting config, routes, view resolution, and publish tags (ServiceProvider.forget_publishes()between tests). - Prefer
almasix.testing.TestCase/boot_application()when the package needs a full app boot — see Testing. - After
pip install -e, resolve a namespaced view (courier::welcome) or hit a package route from your app to confirm discovery and publishing work.
File map
Section titled “File map”| Piece | Path |
|---|---|
| Base provider | src/almasix/providers/provider.py |
| Discovery | src/almasix/providers/package_manifest.py |
| View namespaces | src/almasix/prism/engine.py — add_namespace / :: finder |
| Migration paths | src/almasix/orm/migration.py — register_migration_paths |
| Generator | smith make:package → almasix.smith.make.make_package |
| Scaffold output | packages/{name}/ in your app or repo |