''' just remembered tomatos aren't vegetables. whoops ''' import random import sqlalchemy as sa from co3.schemas import SQLSchema from co3 import CO3, collate, Mapper, ComposableMapper from co3 import util class Vegetable(CO3): def __init__(self, name, color): super().__init__() self.name = name self.color = color #@abstractmethod @collate def cut(self, method): raise NotImplementedError class Tomato(Vegetable): def __init__(self, name, radius): super().__init__(name, 'red') self.radius = radius @property def attributes(self): return vars(self) def collation_attributes(self, key, group): return { 'name': self.name, 'state': key, } @collate('ripe', groups=['aging']) def ripen(self): return { 'age': random.randint(1, 6) } @collate('rotten', groups=['aging']) def rot(self): return { 'age': random.randint(4, 9) } @collate('diced', groups=['cooking']) def dice(self): return { 'pieces': random.randint(2, 12) } @collate def cut(self, method): if method == 'slice': return { 'pieces': random.randint(2, 5) } elif method == 'dice': return self.dice() type_list = [Vegetable, Tomato] ''' VEGETABLE | TOMATO -- AGING | -- COOKING Note: foreign keys need to represent values that could be known by objects _without_ first interacting with a DB. This is slightly non-standard, given how common it is to depend on another table's integer ID (typically a value assigned by the DB using an autoincrement, for example, and not specified explicitly within the insertion body). As a result, SQLTable components need to be able to operate by another unique key when expected to connect to other tables in the hierarchy. Below we use `name` with a UNIQUE constraint for this purpose. Note that having an integer `id` is still perfectly okay so that a table can manage uniqueness of its own rows by default. ''' metadata = sa.MetaData() vegetable_table = sa.Table( 'vegetable', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('name', sa.String, unique=True), sa.Column('color', sa.String), ) tomato_table = sa.Table( 'tomato', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('name', sa.String, util.db.deferred_cd_fkey('vegetable.name'), unique=True), sa.Column('radius', sa.Integer), ) tomato_aging_table = sa.Table( 'tomato_aging_states', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('name', sa.String, util.db.deferred_cd_fkey('tomato.name'), unique=True), sa.Column('state', sa.String), sa.Column('age', sa.Integer), ) tomato_cooking_table = sa.Table( 'tomato_cooking_states', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('name', sa.String, util.db.deferred_cd_fkey('tomato.name'), unique=True), sa.Column('state', sa.String), sa.Column('pieces', sa.Integer), ) vegetable_schema = SQLSchema.from_metadata(metadata) def general_compose_map(c1, c2): return c1.obj.c.name == c2.obj.c.name vegetable_mapper = ComposableMapper( vegetable_schema, attr_compose_map=general_compose_map, coll_compose_map=general_compose_map, ) def attr_name_map(cls): return f'{cls.__name__.lower()}' def coll_name_map(cls, group): return f'{cls.__name__.lower()}_{group}_states' vegetable_mapper.attach_many( type_list, attr_name_map, coll_name_map, ) ''' new mapping type for Mapper attachment: Callable[ [type[CO3], str|None], tuple[str, tuple[str], tuple[str]]] tail tuples to associate column names from central table to collation this should complete the auto-compose horizontally '''