Uvicore Bootstrap¶
Warning
This section is for contributing developers of the uvicore source code. Or for folks who just wish to learn how uvicore works under the hood!
Bootstrap Diagram¶

See The Code on Github
- uvicore/init.py — the pre-bootstrap entrypoint
- uvicore/foundation/application.py — the
Applicationand its three bootstrap phases - uvicore/foundation/events/app.py — the
RegisteredandBootedlifecycle events
Overview¶
Uvicore's bootstrap is a three-phase sequence that turns your application config into a fully wired, running framework. Every package, service, route, model, command and event listener is registered and booted in a deliberate order so that a package's dependencies are always ready before the package itself needs them.
It all starts with a single call from your app's entrypoint:
uvicore.bootstrap(app_config, path, is_console)
That call (in uvicore/__init__.py) initializes the IoC container, sets the earliest core globals (uvicore.ioc, uvicore.app, uvicore.events, uvicore.jobs), then hands off to uvicore.app.bootstrap(), which runs the three phases below and finishes by dispatching the Booted event.
Note
Import order inside uvicore.bootstrap() is deliberate. The IoC container is created from your app_config before Application and the event Dispatcher are imported, which is what lets your app override even the earliest core services. Don't reorder it.
Phase 1 — Build the Provider Graph¶
The first phase (_build_provider_graph) works out which packages need to load and in what order.
-
Start from your
mainpackage — The graph is seeded fromapp_config.main, aDictholding your app'spackageandprovider(and an optional customconfiglocation). -
Walk the dependency tree — Each package's
config/package.pydeclares adependenciesdict of the packages it relies on. Uvicore recurses into those dependencies (depth first) so every upstream package is discovered. -
Last provider wins — If the same package is reached more than once (say two packages both depend on
uvicore.cache), the later definition overwrites the earlier one, but becauseself._providersis anOrderedDictthe original declaration order is preserved. This is exactly how your app transparently overrides a framework package without upsetting dependency ordering. -
App overrides — Finally, any
app_config.overrides.providersyou defined are recursed in last, so your app gets the final say on which provider wins.
The result is a flat, ordered OrderedDict of every provider, stored on self._providers.
Phase 2 — Register All Providers¶
With the graph resolved, Uvicore (_register_providers) iterates every provider in order and calls its register() method.
For each provider it:
- Loads the package's
config/package.pyand builds aPackageobject (name, short name, vendor, version, path) intoself._packages - Instantiates the provider class
- Calls
provider.register()— where lightweight IoC bindings and config merges happen
Once every provider has registered, Uvicore sets self._registered = True and dispatches the Registered event (uvicore.foundation.events.app.Registered).
register() runs before configs are fully merged
At this point the configuration system is not yet complete, so you have no reliable view of the final config. Keep register() to config merges, lightweight IoC bindings and early event listeners only. Note that self.package is deliberately None here — anything substantial belongs in boot().
Phase 3 — Boot All Providers¶
With every package registered and all configs deep-merged, Uvicore (_boot_providers) iterates the providers a second time and calls each boot() method.
For each provider it:
- Reloads the package's
config/package.py(by now the globaluvicore.configis fully deep-merged into one complete view) - Instantiates a fresh provider instance — this time with its fully-built
Packageobject passed in, which is whyself.packageis available inboot()but not inregister() - Calls
provider.boot()— where the real wiring happens
Once every provider has booted, Uvicore sets self._booted = True and dispatches the Booted event (uvicore.foundation.events.app.Booted).
boot() is where everything happens
All configs are deep-merged and self.package is available. Register database connections, models, tables, seeders, Web/API routes, views, assets and CLI commands here — everything substantial goes in boot().
Lifecycle Events¶
Two synchronous events mark the major bootstrap milestones. They're the seam contributors reach for when code needs to run after every package has finished a phase — for example, inspecting the fully assembled route table once everything has booted.
| Event | Dispatched when | is_async |
|---|---|---|
uvicore.foundation.events.app.Registered |
After every register() has completed |
False |
uvicore.foundation.events.app.Booted |
After every boot() has completed |
False |
This is exactly how the HTTP, database and console subsystems bootstrap themselves — rather than forcing provider order, they simply listen for the Booted event and wire up once all configs are merged (see uvicore/http/package/bootstrap.py, uvicore/database/package/bootstrap.py).
Console vs HTTP Mode¶
During bootstrap Uvicore decides whether it is running as a CLI or an HTTP app, exposed as app.is_console, app.is_http and app.is_pytest:
- Console mode (
is_console=True) — CLI commands are registered; the HTTP server is not started. - HTTP mode (
is_console=False) —is_httpis the inverse ofis_console; the HTTP server takes over after bootstrap. - Pytest mode — Auto-detected from the
PYTEST_CURRENT_TESTenvironment variable. Both console and HTTP modes are disabled andis_pytestis set.
A couple of sensible overrides apply:
- Running
./uvicore http serveis technically a CLI command, but it needs the HTTP server live — socommand_is('http serve')forcesis_console=False. - As a failsafe, if the
uvicore.httppackage isn't in the provider graph at all, the app forces console mode sohttp servecan't half-start a server that doesn't exist.
Globals Set During Bootstrap¶
Once bootstrap completes, these convenience globals are available on the uvicore module. ioc, app, events and jobs are set immediately in uvicore/__init__.py; the rest are populated by their respective package providers as they boot.
| Global | Service |
|---|---|
uvicore.ioc |
The IoC container |
uvicore.app |
The Application singleton |
uvicore.events |
The Event dispatcher |
uvicore.jobs |
The Job dispatcher |
uvicore.config |
The merged configuration SuperDict |
uvicore.log |
The Logger |
uvicore.db |
The Database manager |
uvicore.cache |
The Cache manager |
Performance Counter¶
The Application carries a small debug helper, app.perf(item). When app.debug is enabled it appends item to app.perfs and prints it; when debug is off it does nothing. It's a lightweight hook for instrumenting a bootstrap you're debugging — drop app.perf(...) calls into the flow and read the collected list back from app.perfs.
Bootstrap tips
- Three phases, always in this order: build the graph → register everyone → boot everyone.
register()sees an incomplete config and noself.package;boot()sees the fully merged config and a realself.package.- Need to run after all packages finish a phase? Listen for the
RegisteredorBootedevent instead of fighting provider order.