perform minor, partial reformatting
This commit is contained in:
62
test/co3_api_demo.py
Normal file
62
test/co3_api_demo.py
Normal file
@@ -0,0 +1,62 @@
|
||||
'''
|
||||
content -> converted
|
||||
state -> transformed
|
||||
'''
|
||||
|
||||
from co3 import CO3, collate
|
||||
from co3.databases import SQLiteDatabase
|
||||
from co3 import Mapper
|
||||
|
||||
|
||||
class Tomato(CO3):
|
||||
def __init__(self, size, ripe):
|
||||
self.size = size
|
||||
self.ripe = ripe
|
||||
|
||||
@property
|
||||
def attributes(self):
|
||||
return vars(self)
|
||||
|
||||
@collate('diced')
|
||||
def dice(self):
|
||||
return self.size / 2
|
||||
|
||||
@collate('roasted')
|
||||
def roast(self):
|
||||
return self.size / 2
|
||||
|
||||
|
||||
metadata = sa.MetaData()
|
||||
tomato_table = sa.Table(
|
||||
'tomato',
|
||||
metadata,
|
||||
sa.Column('id', sa.Integer, primary_key=True),
|
||||
)
|
||||
tomato_schema = Schema.from_metadata(metadata)
|
||||
|
||||
mapper = Mapper()
|
||||
mapper.attach(
|
||||
Tomato,
|
||||
tomato_table
|
||||
)
|
||||
|
||||
|
||||
tomato = Tomato(5, False)
|
||||
mapper.collect(tomato, for='diced')
|
||||
|
||||
db = SQLiteDatabse('resource.sqlite')
|
||||
db.recreate(tomato_schema)
|
||||
|
||||
# for non-raw DB ops, consider requiring a verify step first. Keeps up integrity between
|
||||
# runs
|
||||
db.verify(tomato_schema)
|
||||
# then
|
||||
# not too straightforward, but can also verify mapper compositions if they use a schema
|
||||
# that's been verified by the database
|
||||
|
||||
db.sync(mapper)
|
||||
|
||||
dict_results = db.select(
|
||||
mapper.compose(Tomato),
|
||||
tomato_table.c.size == 5
|
||||
)
|
||||
5
test/co3_medium_demo.py
Normal file
5
test/co3_medium_demo.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from co3.mediums import Disk
|
||||
|
||||
|
||||
disk = Disk('disk:///')
|
||||
disk.browse('dir://home')
|
||||
152
test/setups/vegetables.py
Normal file
152
test/setups/vegetables.py
Normal file
@@ -0,0 +1,152 @@
|
||||
'''
|
||||
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
|
||||
'''
|
||||
|
||||
39
test/test_co3.py
Normal file
39
test/test_co3.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from co3.components import Relation
|
||||
|
||||
from setups import vegetables as veg
|
||||
|
||||
|
||||
tomato = veg.Tomato('t1', 10)
|
||||
|
||||
def test_co3_registry():
|
||||
keys_to_groups = defaultdict(list)
|
||||
|
||||
# collect groups each key is associated
|
||||
for group, keys in tomato.group_registry.items():
|
||||
for key in keys:
|
||||
keys_to_groups[key].append(group)
|
||||
|
||||
assert set(tomato.key_registry.get(None,{}).keys()) == set(keys_to_groups.get(None,[]))
|
||||
|
||||
# check against `registry`, should map keys to all groups
|
||||
for key, group_dict in tomato.key_registry.items():
|
||||
assert keys_to_groups.get(key) == list(group_dict.keys())
|
||||
|
||||
def test_co3_attributes():
|
||||
assert tomato.attributes is not None
|
||||
|
||||
def test_co3_components():
|
||||
assert tomato.components is not None
|
||||
|
||||
def test_co3_collation_attributes():
|
||||
for group, keys in tomato.group_registry.items():
|
||||
for key in keys:
|
||||
assert tomato.collation_attributes(key, group) is not None
|
||||
|
||||
def test_co3_collate():
|
||||
for group, keys in tomato.group_registry.items():
|
||||
for key in keys:
|
||||
if key is None: continue
|
||||
assert tomato.collate(key, group=group) is not None
|
||||
54
test/test_database.py
Normal file
54
test/test_database.py
Normal file
@@ -0,0 +1,54 @@
|
||||
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)
|
||||
|
||||
# test instance as arg
|
||||
tom_comp = veg.vegetable_mapper.get_attr_comp(tomato)
|
||||
|
||||
inserts = veg.vegetable_mapper.collector.collect_inserts()
|
||||
tom_inserts = inserts.get(tom_comp)
|
||||
assert tom_inserts is not None
|
||||
|
||||
with db.engine.connect() as connection:
|
||||
assert db.manager.insert(
|
||||
connection,
|
||||
tom_comp,
|
||||
tom_inserts,
|
||||
) is not None
|
||||
|
||||
def test_database_insert_many():
|
||||
tomato = veg.Tomato('t2', 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
test/test_imports.py
Normal file
25
test/test_imports.py
Normal 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
test/test_mapper.py
Normal file
56
test/test_mapper.py
Normal 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_attr_comp(veg.Vegetable) is veg_comp
|
||||
assert veg.vegetable_mapper.get_attr_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_coll_comp(veg.Tomato, 'aging') is tom_aging
|
||||
assert veg.vegetable_mapper.get_coll_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
test/test_schema.py
Normal file
26
test/test_schema.py
Normal 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
|
||||
Reference in New Issue
Block a user