Automatic Reflection¶
Let's reflect reflection:
we can reflect tables from database in a model. The next step is to retrieve the tables and create reflection models from it automatically. This can be useful to create interfaces procedural.
For doing so we have selections via Pattern Models:
They contain a Meta with the regex attribute and generate via the template string or function a new ReflectionModel:
from edgy.contrib.autoreflection import AutoReflectModel
class Reflected(AutoReflectModel):
class Meta:
include_pattern = ".*" # regex or string, default .*. Matches against the tablename
exclude_pattern = None # regex or string, default None (disabled). Matches against the tablename
template = "{modelname}{tablename}" # string or function with arguments tablename, modelname, tablekey
databases = (None,) # Restrict reflection to databases. None: main database of registry, string extra databases of registry
schemes = (None,) # Which schemes to checks
Note: when a reflected model is generated the meta is switched in the copy to a regular MetaInfo.
Meta parameters¶
Note: we use the table name not the table key.
What is the difference:
table name: tablename
table key: schema.tablename
That is because edgy handle schemes a little bit different:
In edgy a model can exist in multiple schemes and the scheme is explicit selected.
Inclusion & Exclusion patterns¶
Here we can specify patterns against which a regex is checked. By default the include_pattern
is set to
.*
which matches all tablenames and the exclude_pattern
is disabled.
The include_pattern
will convert all falsy values to the match all, while the exclude_pattern will be really disabled.
Template¶
Can be a function which takes the tablename as parameter and is required to return a string.
Or it can be a format string with the possible parameters:
- tablename: the name of the table
- tablekey: the key (name with scheme) of the table
- modelname: the model name
Databases¶
In the registry you specify a main database (which is here None) and via the extra dictionary multiple named databases.
The extra databases can be selected via their name while the main can be selected by None
.
This controls from which database the models are reflected. This is useful to extract data from other databases and to use it in the main application.
By default the autoreflection only uses the main databases.
Schemes¶
This parameter is providing the schemes which should be scanned for models.
This parameter is required when the models which should be reflected are in a different schema.
Examples¶
Procedural interface¶
To build an application there is also a data driven approach. Instead of defining relations and fields by hand they are all automatically generated.
For creating the tables we can use:
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 we can reflect:
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())
Legacy databases¶
Suppose you have a new modern database, a legacy database and an ancient database which very few capabilities from which both you need data. In the legacy and ancient database, you are only allowed to update some specific fields.
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())