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 toNone
(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:
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:
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.
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.