add inital test suite, fix minor Mapping group bug

This commit is contained in:
2024-04-18 02:06:42 -07:00
parent 5f2be91c5d
commit b369428eb3
16 changed files with 233 additions and 1367 deletions

View File

@@ -1,19 +0,0 @@
from co3 import Accessor
from co3 import CO3
from co3 import Collector
from co3 import Database
from co3 import Indexer
from co3 import Manager
from co3 import Mapper
from co3 import Relation
from co3.accessors import SQLAccessor
from co3.databases import SQLDatabase
from co3.databases import SQLiteDatabase
from co3.managers import SQLManager
from co3.relations import TabularRelation
from co3.relations import SQLTable

135
tests/setups/vegetables.py Normal file
View File

@@ -0,0 +1,135 @@
'''
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):
self.name = name
self.color = color
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, action_key, action_grounp):
return {
'name': self.name,
'state': action_key,
}
@collate('ripe', action_groups=['aging'])
def ripen(self):
return {
'age': random.randint(1, 6)
}
@collate('rotten', action_groups=['aging'])
def rot(self):
return {
'age': random.randint(4, 9)
}
@collate('diced', action_groups=['cooking'])
def dice(self):
return {
'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, 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, action_group):
return f'{cls.__name__.lower()}_{action_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
'''

57
tests/test_co3.py Normal file
View File

@@ -0,0 +1,57 @@
from co3.components import Relation
from setups import vegetables as veg
def test_mapper_getters():
veg_comp = veg.vegetable_schema.get_component('vegetable')
tom_comp = veg.vegetable_schema.get_component('tomato')
assert veg.vegetable_mapper.get_attribute_comp(veg.Vegetable) is veg_comp
assert veg.vegetable_mapper.get_attribute_comp(veg.Tomato) is tom_comp
tom_aging = veg.vegetable_schema.get_component('tomato_aging_states')
tom_cooking = veg.vegetable_schema.get_component('tomato_cooking_states')
assert veg.vegetable_mapper.get_collation_comp(veg.Tomato, 'aging') is tom_aging
assert veg.vegetable_mapper.get_collation_comp(veg.Tomato, 'cooking') is tom_cooking
def test_mapper_attach():
assert veg.vegetable_mapper.attach(
veg.Tomato,
'tomato',
coll_groups={
'aging': 'tomato_aging_states',
'cooking': 'tomato_cooking_states',
},
) is None
def test_mapper_attach_many():
assert veg.vegetable_mapper.attach_many(
[veg.Vegetable, veg.Tomato],
lambda t: f'{t.__name__.lower()}'
) is None
def test_mapper_collect():
tomato = veg.Tomato('t1', 10)
receipts = veg.vegetable_mapper.collect(tomato)
assert len(receipts) == 2
# attempt to retrieve receipts one at a time
res1 = veg.vegetable_mapper.collector.collect_inserts([receipts[0]])
assert len(res1) == 1 # should be just one match
assert len(res1[next(iter(res1.keys()))]) == 1 # and one dict for matching comp
# try again, check no persistent match
res1 = veg.vegetable_mapper.collector.collect_inserts([receipts[0]])
assert len(res1) == 0 # should be no matches for the same receipt
res2 = veg.vegetable_mapper.collector.collect_inserts([receipts[1]])
assert len(res2) == 1
assert len(res2[next(iter(res2.keys()))]) == 1

36
tests/test_database.py Normal file
View File

@@ -0,0 +1,36 @@
from co3.components import Relation
from co3.databases import SQLDatabase
from setups import vegetables as veg
db = None
def test_database_init():
global db
db = SQLDatabase('sqlite://')
assert True
def test_database_recreate():
db.recreate(veg.vegetable_schema)
assert True
def test_database_insert():
tomato = veg.Tomato('t1', 5)
veg.vegetable_mapper.collect(tomato)
with db.engine.connect() as connection:
assert db.manager.insert_many(
connection,
veg.vegetable_mapper.collector.inserts,
) is not None
def test_database_access():
agg_table = veg.vegetable_mapper.compose(veg.Tomato)
with db.engine.connect() as connection:
assert db.accessor.select(
connection,
agg_table,
) is not None

25
tests/test_imports.py Normal file
View File

@@ -0,0 +1,25 @@
def test_import():
from co3 import Accessor
from co3 import CO3
from co3 import Collector
from co3 import Database
from co3 import Indexer
from co3 import Manager
from co3 import Mapper
from co3 import Component
from co3.accessors import SQLAccessor
from co3.accessors import FTSAccessor
from co3.accessors import VSSAccessor
from co3.databases import SQLDatabase
from co3.databases import SQLiteDatabase
from co3.managers import SQLManager
from co3.components import ComposableComponent
from co3.components import Relation
from co3.components import SQLTable
assert True

56
tests/test_mapper.py Normal file
View File

@@ -0,0 +1,56 @@
from co3.components import Relation
from setups import vegetables as veg
def test_mapper_getters():
veg_comp = veg.vegetable_schema.get_component('vegetable')
tom_comp = veg.vegetable_schema.get_component('tomato')
assert veg.vegetable_mapper.get_attribute_comp(veg.Vegetable) is veg_comp
assert veg.vegetable_mapper.get_attribute_comp(veg.Tomato) is tom_comp
tom_aging = veg.vegetable_schema.get_component('tomato_aging_states')
tom_cooking = veg.vegetable_schema.get_component('tomato_cooking_states')
assert veg.vegetable_mapper.get_collation_comp(veg.Tomato, 'aging') is tom_aging
assert veg.vegetable_mapper.get_collation_comp(veg.Tomato, 'cooking') is tom_cooking
def test_mapper_attach():
assert veg.vegetable_mapper.attach(
veg.Tomato,
'tomato',
coll_groups={
'aging': 'tomato_aging_states',
'cooking': 'tomato_cooking_states',
},
) is None
def test_mapper_attach_many():
assert veg.vegetable_mapper.attach_many(
[veg.Vegetable, veg.Tomato],
lambda t: f'{t.__name__.lower()}'
) is None
def test_mapper_collect():
tomato = veg.Tomato('t1', 10)
receipts = veg.vegetable_mapper.collect(tomato)
assert len(receipts) == 2
# attempt to retrieve receipts one at a time
res1 = veg.vegetable_mapper.collector.collect_inserts([receipts[0]])
assert len(res1) == 1 # should be just one match
assert len(res1[next(iter(res1.keys()))]) == 1 # and one dict for matching comp
# try again, check no persistent match
res1 = veg.vegetable_mapper.collector.collect_inserts([receipts[0]])
assert len(res1) == 0 # should be no matches for the same receipt
res2 = veg.vegetable_mapper.collector.collect_inserts([receipts[1]])
assert len(res2) == 1
assert len(res2[next(iter(res2.keys()))]) == 1

26
tests/test_schema.py Normal file
View File

@@ -0,0 +1,26 @@
from co3.components import Relation
from setups import vegetables as veg
def test_schema_get():
veg_comp_raw = veg.vegetable_schema._component_map.get('vegetable')
veg_comp = veg.vegetable_schema.get_component('vegetable')
assert veg_comp_raw is veg_comp
def test_schema_contains():
vegetable_comp = veg.vegetable_schema.get_component('vegetable')
tomato_comp = veg.vegetable_schema.get_component('tomato')
tomato_aging_comp = veg.vegetable_schema.get_component('tomato_aging_states')
assert vegetable_comp in veg.vegetable_schema
assert tomato_comp in veg.vegetable_schema
assert tomato_aging_comp in veg.vegetable_schema
def test_schema_add():
veg.vegetable_schema.add_component(Relation[int]('a', 1))
veg.vegetable_schema.add_component(Relation[int]('b', 2))
assert veg.vegetable_schema.get_component('a') is not None
assert veg.vegetable_schema.get_component('b') is not None