flesh out general Collector/Mapper interaction

This commit is contained in:
2024-04-12 03:16:03 -07:00
parent fb69bb1782
commit 753b67a51f
11 changed files with 500 additions and 150 deletions

View File

@@ -1,9 +1,12 @@
'''
just remembered tomatos aren't vegetables. whoops
'''
import random
import sqlalchemy as sa
from co3.schemas import SQLSchema
from co3 import CO3, collate
from co3 import CO3, collate, Mapper
from co3 import util
@@ -20,6 +23,12 @@ class Tomato(Vegetable):
@property
def attributes(self):
return vars(self)
def collation_attributes(self, action_key, action_grounp):
return {
'name': self.name,
'state': action_key,
}
@collate('ripe', action_groups=['aging'])
def ripen(self):
@@ -39,42 +48,58 @@ class Tomato(Vegetable):
'pieces': random.randint(2, 12)
}
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),
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('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
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('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
sa.Column('name', sa.String, util.db.deferred_cd_fkey('tomato.name'), unique=True),
sa.Column('state', sa.String),
sa.Column('age', sa.Integer),
sa.Column('age', sa.Integer),
)
tomato_cooking_table = sa.Table(
'tomato_cooking_states',
metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
sa.Column('state', sa.String),
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)
vegetable_schema = SQLSchema.from_metadata(metadata)
vegetable_mapper = Mapper(vegetable_schema)

View File

@@ -16,8 +16,6 @@
}
],
"source": [
"from co3 import Mapper\n",
"\n",
"import vegetables"
]
},
@@ -31,13 +29,38 @@
"- Need connective function (type to collation) and attribute map. Do we need to this with a subclass? If a func is passed in on init, I can type it appropriately I guess `Callable[[type[CO3],str,str|None],dict]`"
]
},
{
"cell_type": "markdown",
"id": "ef733715-bb75-4263-b216-45e778a06b21",
"metadata": {},
"source": [
"## Usage\n",
"The Mapper's primary job is to associate class hierarchies with database components. This can be done in a few ways:\n",
"\n",
"1. Manually attaching a type reference to a Component\n",
"2. Attaching a type reference to a Component's name as registered in a schema\n",
"3. Automatically register the CO3 heirarchy to matching schema component names (through transformation)"
]
},
{
"cell_type": "markdown",
"id": "d2672422-3596-4eab-ac44-5da617f74b80",
"metadata": {
"jp-MarkdownHeadingCollapsed": true
},
"source": [
"## Explicit example steps"
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 2,
"id": "7d80f7b9-7458-4ad4-8c1a-3ea56e796b4e",
"metadata": {},
"outputs": [],
"source": [
"from co3 import Mapper\n",
"\n",
"vegetable_mapper = Mapper(\n",
" vegetables.Vegetable,\n",
" vegetables.vegetable_schema\n",
@@ -46,21 +69,34 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 3,
"id": "d24d31b4-c4a6-4a1e-8bea-c44378aadfdd",
"metadata": {},
"outputs": [],
"outputs": [
{
"data": {
"text/plain": [
"'\\nvegetable_mapper.attach(\\n vegetables.Vegetable,\\n vegetables.vegetable_table,\\n)\\n'"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# not valid; tables need to be wrapped in CO3 Components\n",
"'''\n",
"vegetable_mapper.attach(\n",
" vegetables.Vegetable,\n",
" vegetables.vegetable_table,\n",
")"
")\n",
"'''"
]
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 4,
"id": "f9408562-bf50-4522-909c-318557f85948",
"metadata": {},
"outputs": [],
@@ -78,7 +114,7 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 5,
"id": "05fdd404-87ee-4187-832f-2305272758ae",
"metadata": {},
"outputs": [],
@@ -96,21 +132,43 @@
},
{
"cell_type": "code",
"execution_count": null,
"execution_count": 6,
"id": "e9b6af49-a69d-41cc-beae-1b6f171cd2f5",
"metadata": {},
"outputs": [],
"source": [
"# attach entire type hierarchy w/ type->name map\n",
"vegetable_mapper.attach_hierarchy(\n",
"# this might make more sense during init\n",
" lambda x:x.__name__.lower())\n",
" # this might make more sense during init\n",
" vegetables.Vegetable,\n",
" lambda x:x.__name__.lower()\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "0fb45a86-5c9b-41b1-a3ab-5691444f175e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<co3.components.SQLTable at 0x7f2012b23f80>"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vegetable_mapper.get_collation_comp(vegetables.Tomato, 'cooking')"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "2e4336ab-5b5f-484d-815d-164d4b6f40a0",
"metadata": {},
"outputs": [
@@ -118,16 +176,16 @@
"data": {
"text/plain": [
"{'co3_root': vegetables.Vegetable,\n",
" 'schema': <co3.schemas.SQLSchema at 0x74ac03f5c8c0>,\n",
" 'collector': <co3.collector.Collector at 0x74ac0357ae70>,\n",
" 'composer': <co3.composer.Composer at 0x74ac0357a4b0>,\n",
" 'attribute_comps': {vegetables.Tomato: <co3.components.SQLTable at 0x74ac09d4a720>},\n",
" 'schema': <co3.schemas.SQLSchema at 0x732074224aa0>,\n",
" 'collector': <co3.collector.Collector at 0x7320757da120>,\n",
" 'composer': <co3.composer.Composer at 0x7320757da9c0>,\n",
" 'attribute_comps': {vegetables.Tomato: <co3.components.SQLTable at 0x732074224cb0>},\n",
" 'collation_groups': defaultdict(dict,\n",
" {vegetables.Tomato: {'aging': <co3.components.SQLTable at 0x74ac03f5cad0>,\n",
" 'cooking': <co3.components.SQLTable at 0x74ac03f5cb00>}})}"
" {vegetables.Tomato: {'aging': <co3.components.SQLTable at 0x732074224ce0>,\n",
" 'cooking': <co3.components.SQLTable at 0x732074224d10>}})}"
]
},
"execution_count": 9,
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
@@ -137,40 +195,187 @@
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "c16786d4-0b71-42d9-97f7-7893c542104e",
"cell_type": "markdown",
"id": "47859e25-b803-4459-a581-f10bbcfac716",
"metadata": {},
"outputs": [],
"source": [
"tomato = vegetables.Tomato('t1', 5)"
"## Holistic attachment"
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "884d6753-c763-4e71-824a-711436e203e1",
"execution_count": 2,
"id": "70c9baed-b870-4021-8949-9b713d863de6",
"metadata": {},
"outputs": [],
"source": [
"def attr_name_map(cls):\n",
" return f'{cls.__name__.lower()}'\n",
"\n",
"def coll_name_map(cls, action_group):\n",
" return f'{cls.__name__.lower()}_{action_group}_states'\n",
"\n",
"vegetables.vegetable_mapper.attach_many(\n",
" vegetables.type_list,\n",
" attr_name_map,\n",
" coll_name_map,\n",
")"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "c16786d4-0b71-42d9-97f7-7893c542104e",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<vegetables.Tomato at 0x74ac082bacc0>"
"{'age': 4}"
]
},
"execution_count": 11,
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"tomato"
"# create new CO3 descendant\n",
"tomato = vegetables.Tomato('t1', 5)\n",
"\n",
"# test a register collation action\n",
"tomato.collate('ripe')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "d7fa94ca-3ecd-4ee3-b0dc-f3b2b65ee47c",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<Component (SQLTable)> tomato"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vegetables.vegetable_mapper.get_attribute_comp(vegetables.Tomato)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "1adc3bc5-957f-4b5a-bc2c-2d172675826d",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'schema': <co3.schemas.SQLSchema at 0x7ab568224e60>,\n",
" 'collector': <co3.collector.Collector at 0x7ab568225190>,\n",
" 'composer': <co3.composer.Composer at 0x7ab5682251c0>,\n",
" 'attribute_comps': {vegetables.Vegetable: <Component (SQLTable)> vegetable,\n",
" vegetables.Tomato: <Component (SQLTable)> tomato},\n",
" 'collation_groups': defaultdict(dict,\n",
" {vegetables.Vegetable: {},\n",
" vegetables.Tomato: {'aging': <Component (SQLTable)> tomato_aging_states,\n",
" 'cooking': <Component (SQLTable)> tomato_cooking_states}})}"
]
},
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vars(vegetables.vegetable_mapper)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "f32d1f65-9b1d-4600-b396-8551fbd1fcf7",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"['3bf42abc-8a12-452f-baf6-38a05fc5d420',\n",
" '271b7b84-846e-4d1d-87f6-bcabc90a7b55',\n",
" 'f9fc5d16-c5cb-47a7-9eca-7df8a3ba5d10']"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vegetables.vegetable_mapper.collect(tomato, ['ripe'])"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "380dfbea-90cc-49fc-aef1-ebb342872632",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"defaultdict(<function co3.collector.Collector.__init__.<locals>.<lambda>()>,\n",
" {'3bf42abc-8a12-452f-baf6-38a05fc5d420': (<Component (SQLTable)> vegetable,\n",
" {'name': 't1', 'color': 'red'}),\n",
" '271b7b84-846e-4d1d-87f6-bcabc90a7b55': (<Component (SQLTable)> tomato,\n",
" {'name': 't1', 'radius': 5}),\n",
" 'f9fc5d16-c5cb-47a7-9eca-7df8a3ba5d10': (<Component (SQLTable)> tomato_aging_states,\n",
" {'name': 't1', 'state': 'ripe', 'age': 1})})"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vegetables.vegetable_mapper.collector._inserts"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "905bb2a9-9c22-4187-be15-3dd32d206e26",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{<Component (SQLTable)> vegetable: [{'name': 't1', 'color': 'red'}],\n",
" <Component (SQLTable)> tomato: [{'name': 't1', 'radius': 5}],\n",
" <Component (SQLTable)> tomato_aging_states: [{'name': 't1',\n",
" 'state': 'ripe',\n",
" 'age': 1}]}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"vegetables.vegetable_mapper.collector.inserts"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "137d0bf1-940d-448c-91e9-01e7fc4f31b4",
"id": "d166b9af-e3ba-4750-9dcb-d8d4e08fe4d3",
"metadata": {},
"outputs": [],
"source": []

View File

@@ -1,9 +1,12 @@
'''
just remembered tomatos aren't vegetables. whoops
'''
import random
import sqlalchemy as sa
from co3.schemas import SQLSchema
from co3 import CO3, collate
from co3 import CO3, collate, Mapper
from co3 import util
@@ -20,6 +23,12 @@ class Tomato(Vegetable):
@property
def attributes(self):
return vars(self)
def collation_attributes(self, action_key, action_grounp):
return {
'name': self.name,
'state': action_key,
}
@collate('ripe', action_groups=['aging'])
def ripen(self):
@@ -39,42 +48,58 @@ class Tomato(Vegetable):
'pieces': random.randint(2, 12)
}
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),
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('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
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('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
sa.Column('name', sa.String, util.db.deferred_cd_fkey('tomato.name'), unique=True),
sa.Column('state', sa.String),
sa.Column('age', sa.Integer),
sa.Column('age', sa.Integer),
)
tomato_cooking_table = sa.Table(
'tomato_cooking_states',
metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('vegetable_id', sa.Integer, util.db.deferred_cd_fkey('vegetables.id')),
sa.Column('state', sa.String),
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)
vegetable_schema = SQLSchema.from_metadata(metadata)
vegetable_mapper = Mapper(vegetable_schema)