Skip to content

Architecture

QuadKit’s architecture relies on explicit package boundaries. The rules vary by package family and are checked in the repository’s import-linter configuration.

QuadKit is published as separate distributions under the shared quadkit namespace. The intended dependency shape is not a single strict three-layer ladder: the repository checks different boundaries for core, foundational packages, regular extensions, AI/multimedia sub-packages, and composite packages such as admin, CLI, and testing.

  • quadkit-contracts is the shared home for cross-package protocols, types, and exceptions. The boundary check prevents it from importing QuadKit implementation packages; it does have a third-party typing-extensions dependency.
  • Core is intended to stay independent of extension packages. The current import-boundary configuration records a small set of tracked exceptions.
  • The foundational packages (domain, serialization, validation, and concurrency) can be used by extensions but are checked against imports from higher-level packages.
  • Regular extensions are checked for peer independence. Some concrete edges are explicitly tracked or allowed; composite packages and orchestrators have their own contracts rather than the same peer rule.

The executable rules live in the repository’s root .importlinter file. Run them with make lint-boundaries (the namespace-aware wrapper); the bare lint-imports command does not discover this namespace package layout. For package dependencies and install extras, see Compatibility.


2. Shared contracts and implementation boundaries

Section titled “2. Shared contracts and implementation boundaries”

A package should depend on a shared contract rather than another package’s implementation when the interface is intended to be reused. Package-local protocol modules are internal seams; the boundary rules specifically check that known local protocols are not imported across package boundaries.

This is a design direction, not a claim that every cross-package import is currently forbidden: the configured contracts include composite-package allowlists and explicitly tracked imports. Consult .importlinter for the current rule set instead of assuming that all quadkit-* packages are independent peers.


The package graph above is not the application tree. quadkit new project currently offers one api starter and writes the canonical shape. Add installed packages with quadkit add; the layout does not change. There is no --structure flag and no models/ directory.

KindWhere it lives
Composition rootsrc/<app>/app.py — create_app(), ASGI target <app>.app:app
Unscoped domain typessrc/<app>/domains/
App providerssrc/<app>/di/*_provider.py
Module providersrc/<app>/modules/<slug>/provider.py
Cross-cuttingsrc/<app>/shared/

If a skill or the CLI dump disagrees, this site wins. Full map: Project Structure.


All packages publish into the shared quadkit import namespace (a PEP 420 namespace package), even though they are separate distributions:

quadkit-web/ → src/quadkit/web/ → import: from quadkit.web import ...
quadkit-testing/ → src/quadkit/testing/ → import: from quadkit.testing import ...
quadkit-cli/ → src/quadkit/cli/ → import: from quadkit.cli import ...

So installing the quadkit-web distribution gives you the quadkit.web module. One consistent import root; many independently versioned packages underneath.

from quadkit import Application, Provider # core
from quadkit.web import WebModule, get # quadkit-web distribution
from quadkit.contracts.core.di import BootContainerProtocol # contracts

An extension contributes to an application in three ways, all built on the core primitives:

MechanismRoleCovered in
ProviderRegisters the extension’s services in the container and manages their lifecycleProviders
ModuleBundles providers with import/export boundaries; usually exposes configure()Modules
ContractThe protocol(s) the extension implements or depends on, defined in quadkit-contractsContainer Protocols

Most extensions ship a configure() classmethod on their module so you add them in one line. The scaffold uses modules — create_app() in src/<app>/app.py:

from quadkit import Application
from quadkit.web import WebModule
app = Application(name="my-app")
app.add_module(WebModule.configure(discover=["my_app.controllers", "my_app.modules"]))

WebModule.configure() constructs a WebProvider internally. You rarely add that provider by hand.

Boot order follows provider priority, so infrastructure providers are ready before the web layer starts serving. Full tree: Project Structure.


PropertyHow the package design supports it
TestabilityDepend on contracts → substitute fakes from quadkit-testing with no production code change.
ReplaceabilitySwap one backend for another through config, not refactors — the package you replace is never referenced by name.
Incremental adoptionStart from the api scaffold and add other installed extensions as needed.
Clear ownershipEach package has one purpose and a well-defined surface; large teams can own packages independently.