Skip to content

Architecture Overview

Edgy is intentionally small at the API surface but rich in internals. This page explains the moving parts, why they exist, and how they interact at runtime.

What Is the Runtime Architecture?

At runtime, Edgy is mostly these components:

  • Settings (EdgySettings): Controls migrations, storage, shell, and runtime behavior.
  • Monkay instance (edgy.monkay.instance): The active application context (registry, optional app, storages).
  • Registry: The command center for models, database connections, metadata, and schema operations.
  • Models and QuerySets: Declarative models and immutable query pipelines.
  • Signals and callbacks: Lifecycle hooks for save/update/delete/migrate paths.

If you already use Edgy comfortably, this page is mainly for when you need to reason about lifecycle, advanced integrations, or debugging.

Runtime Map

graph TD
    A["Settings (EdgySettings)"] --> B["Monkay Instance"]
    B --> C["Registry"]
    C --> D["Model Metadata"]
    D --> E["QuerySet Build"]
    E --> F["Database (main/extra)"]
    F --> G["Rows -> Model Parsing"]
    G --> H["Signals / Callbacks"]

The diagram is the high-level flow. For setup details, read Connection Management. For schema/database switching, read Queries.

Why It Is Structured This Way

The design optimizes for:

  • Framework agnosticism: Edgy works with ASGI apps and non-ASGI/sync contexts.
  • Predictable lifecycle: Databases are connected/disconnected through explicit registry scopes.
  • Composable behavior: Signals, callbacks, and settings let you customize without rewriting core logic.
  • Safe multi-tenancy/multi-db: Schema and connection switching is first-class in QuerySet and Registry.

When You Need to Know This

You usually need architectural context when:

  • integrating Edgy in a custom framework setup,
  • running Edgy from sync code (run_sync + with_async_env),
  • using multiple registries/databases/schemas,
  • debugging lifecycle warnings such as DatabaseNotConnectedWarning,
  • extending model registration behavior with callbacks.

How The Pieces Fit

1. Startup and Ambient Context

You initialize a Registry, create/wrap your app, then set edgy.monkay.instance.

from ravyn import Ravyn

from edgy import Registry, Instance, monkay

models = Registry(database="sqlite:///db.sqlite", echo=True)


app = models.asgi(
    Ravyn(
        routes=[...],
    )
)

# load settings
monkay.evaluate_settings(ignore_import_errors=False)
# monkey-patch app so you can use edgy shell
monkay.set_instance(Instance(registry=models, app=app))

2. Connection Lifecycle

The registry is the lifecycle boundary:

  • async with registry: in async-native code.
  • with registry.with_async_env(): in sync code.
from edgy import Registry, Instance, monkay, run_sync

models = Registry(database="sqlite:///db.sqlite", echo=True)


# load settings
monkay.evaluate_settings(ignore_import_errors=False)
# monkey-patch app so you can use edgy shell
monkay.set_instance(Instance(registry=models))


def main() -> None:
    with models.with_async_env():
        run_sync(User.query.all())

3. Query Execution

Model.query creates a QuerySet. QuerySet methods clone state and compile to SQL only on execution (await, .get(), .all(), etc.).

4. Result Parsing

Rows are parsed back into model instances, including relationship handling (select_related, prefetch_related) and schema/db context.

Query/Request Lifecycle (High Level)

  1. App enters lifecycle scope (registry connected).
  2. QuerySet is built from model manager (Model.query...).
  3. QuerySet compiles SQL from model metadata + active schema/db.
  4. Query is executed through the selected Database.
  5. Result rows are parsed into model instances.
  6. Hooks/signals run where applicable (save/update/delete/migrate).
  7. Lifecycle exits and registry disconnects databases.

Query Lifecycle Diagram

sequenceDiagram
    participant App
    participant Registry
    participant QuerySet
    participant DB as Database
    participant Model

    App->>Registry: enter lifecycle (connect)
    App->>QuerySet: build query (Model.query...)
    QuerySet->>DB: compile + execute SQL
    DB-->>QuerySet: rows
    QuerySet-->>Model: parse rows to instances
    Model-->>App: return model objects
    App->>Registry: exit lifecycle (disconnect)

Extension Points

Common extension points:

  • Settings: preloads, extensions, migration behavior, storage, concurrency limits.
  • Signals: pre/post save, update, delete, migrate.
  • Registry callbacks: mutate or enrich models when registered.
  • Model-level hooks: custom save/delete behavior and admin marshall customization.

Practical Advice

  • Keep a long-lived registry scope in servers/workers.
  • Avoid opening/closing DB contexts around every single operation.
  • Use using(database=..., schema=...) explicitly in multi-db/multi-schema code paths.
  • Prefer context-managed schema/tenant switching (with_schema, with_tenant).

See Also