Skip to content

Automatic Reflection

Automatic reflection in Edgy allows you to dynamically generate models from existing database tables. This is particularly useful for creating procedural interfaces or integrating with legacy databases.

Edgy provides AutoReflectModel to automate this process, using pattern matching to select tables and customizable templates to generate model names.

from edgy.contrib.autoreflection import AutoReflectModel

class Reflected(AutoReflectModel):
    class Meta:
        include_pattern = ".*"  # Regex or string, matches table names (default: ".*")
        exclude_pattern = None  # Regex or string, excludes table names (default: None)
        template = "{modelname}{tablename}"  # String or function for model name generation
        databases = (None,)  # Databases to reflect (None: main database, string: extra database)
        schemes = (None,) # Schemes to check for tables.

When a reflected model is generated, its Meta class is converted to a regular MetaInfo.

Meta Parameters Explained

Understanding the Meta parameters is crucial for effective automatic reflection.

Key Concepts:

  • Table Name: The actual name of the table in the database.
  • Table Key: The fully qualified name of the table, including the schema (e.g., schema.tablename).

Edgy handles schemas differently, allowing a model to exist in multiple schemas, with explicit schema selection during queries.

Inclusion & Exclusion Patterns

These parameters use regular expressions to filter tables for reflection.

  • include_pattern: Matches table names to include. Defaults to .* (all tables). Falsy values are converted to the match-all pattern.
  • exclude_pattern: Matches table names to exclude. Defaults to None (disabled).

Template

The template parameter controls how model names are generated. It can be:

  • A function: Takes the table name as an argument and returns a string.
  • A format string: Uses placeholders like {tablename}, {tablekey}, and {modelname}.

Databases

The databases parameter specifies which databases to reflect.

  • None: The main database defined in the registry.
  • String: The name of an extra database defined in the registry's extra dictionary.

By default, only the main database is reflected.

Schemes

The schemes parameter specifies which database schemas to scan for tables.

This is required when the tables to be reflected are located in a schema other than the default.

Examples: Practical Applications of Automatic Reflection

Let's explore some practical examples of how automatic reflection can be used.

Procedural Interface Generation

Imagine you need to create an application with a data-driven approach, where models are generated automatically from existing tables.

First, create the tables using:

source.py
import edgy

registry = edgy.Registry(database="sqlite:///webshopdb.sqlite")


class Trouser(edgy.Model):
    price = edgy.DecimalField(max_digits=2, decimal_places=2)
    name = edgy.CharField(max_length=50)
    with_pocket = edgy.BooleanField()
    size = edgy.IntegerField()

    class Meta:
        registry = registry


class Shoe(edgy.Model):
    price = edgy.DecimalField(decimal_places=2)
    name = edgy.CharField(max_length=50)
    waterproof = edgy.BooleanField()
    size = edgy.FloatField()

    class Meta:
        registry = registry


async def main():
    async with registry:
        await registry.create_all()
        await Trouser.query.create(price=10.50, name="Fancy Jeans", with_pocket=True, size=30)
        await Shoe.query.create(price=14.50, name="SuperEliteWalk", waterproof=False, size=10.5)


edgy.run_sync(main())

Then, reflect the tables into models:

procedural.py
import edgy
from edgy.contrib.autoreflection import AutoReflectModel

reflected = edgy.Registry(database="sqlite:///webshopdb.sqlite")


class Product(AutoReflectModel):
    price = edgy.DecimalField(decimal_places=2)
    name = edgy.CharField(max_length=50)

    class Meta:
        registry = reflected
        template = r"{modelname}_{tablename}"


async def main():
    async with reflected:
        print(
            *[
                product.model_dump()
                async for product in reflected.get_model("Product_shoes").query.all()
            ]
        )
        print(
            *[
                product.model_dump()
                async for product in reflected.get_model("Product_trousers").query.all()
            ]
        )
        await reflected.get_model("Product_shoes").query.update(
            price=reflected.get_model("Product_shoes").table.c.price + 10
        )


edgy.run_sync(main())

Integrating with Legacy Databases

Suppose you have a modern database, a legacy database, and an ancient database. You need to access data from all three, but you're only allowed to update specific fields in the legacy and ancient databases.

legacy.py
import edgy
from edgy.contrib.autoreflection import AutoReflectModel

registry = edgy.Registry(
    database="sqlite:///newapp.sqlite",
    extra={
        "legacy": "sqlite:///webshopdb.sqlite",
        "ancient": "sqlite:///webshopdb.sqlite",
    },
)


class Product(AutoReflectModel):
    price = edgy.DecimalField(decimal_places=2)
    name = edgy.CharField(max_length=50)

    class Meta:
        registry = registry
        template = r"{modelname}_{tablename}"
        databases = ("legacy",)


class AncientProduct(edgy.ReflectModel):
    price = edgy.DecimalField(decimal_places=2)
    name = edgy.CharField(max_length=50)
    __using_schema__ = None

    class Meta:
        registry = registry
        tablename = "shoes"


AncientProduct.database = registry.extra["ancient"]


async def main():
    async with registry:
        print(*[product.model_dump() async for product in AncientProduct.query.all()])
        print(
            *[
                product.model_dump()
                async for product in registry.get_model("Product_shoes").query.all()
            ]
        )
        print(
            *[
                product.model_dump()
                async for product in registry.get_model("Product_trousers").query.all()
            ]
        )
        await registry.get_model("Product_shoes").query.update(
            price=registry.get_model("Product_shoes").table.c.price + 10
        )


edgy.run_sync(main())

In this example, LegacyModel and AncientModel are automatically generated from the legacy and ancient databases, allowing you to access their data while adhering to their update restrictions.