async def test_relationship_generation(self): from sqlalchemy import MetaData, Integer from fox_orm import OrmModel from fox_orm.fields import pk from fox_orm.relations import ManyToMany metadata = MetaData() class RelA(OrmModel): __metadata__ = metadata pkey: Optional[int] = pk b_objs: ManyToMany['B'] = ManyToMany(to='placeholder', via='mid') class RelB(OrmModel): __metadata__ = metadata pkey: Optional[int] = pk a_objs: ManyToMany['A'] = ManyToMany(to=RelA, via='mid') RelA.__relations__['b_objs']._to = RelB FoxOrm.init_relations(metadata) self.assertIn('mid', metadata.tables) self.assertNotIn('b_objs', metadata.tables['rel_a'].columns) self.assertNotIn('a_objs', metadata.tables['rel_b'].columns) self.assertEqual(schema_to_set(metadata.tables['mid']), { ('rel_a_id', Integer), ('rel_b_id', Integer), })
def _init(self, metadata: MetaData, _from: 'Type[OrmModel]'): self._from = _from if isinstance(self._to, str): self._to = full_import(self._to) self._via, self._this_id, self._other_id = FoxOrm.get_assoc_table( metadata, self._from, self._to, self._via_name ) self._initialized = True
import asyncio import os from time import time from sqlalchemy import create_engine from fox_orm import FoxOrm from tests.models import A DB_FILE = 'test.db' DB_URI = 'sqlite:///test.db' if os.path.exists(DB_FILE): os.remove(DB_FILE) FoxOrm.init(DB_URI) FoxOrm.metadata.create_all(create_engine(DB_URI)) ITERATIONS = 300 async def main(): print('Simple insert') time_start = time() for i in range(ITERATIONS): await FoxOrm.db.execute(A.__table__.insert(), { 'text': 'test', 'n': i, }) print('- Databases', (time() - time_start) / ITERATIONS) time_start = time()
a_objs: ManyToMany[A] = ManyToMany(to='tests.models.A', via='mid') c_objs: OneToMany['C'] = OneToMany(to='tests.models.C', key='b_id') class C(OrmModel): pkey: Optional[int] = pk b_id: Optional[int] d_id: Optional[int] class D(OrmModel): pkey: Optional[int] = pk c_objs: OneToMany['C'] = OneToMany(to='tests.models.C', key='d_id') class E(OrmModel): pkey: Optional[int] = pk dt: datetime.datetime class ExtraFields(OrmModel): class Config: extra = Extra.allow pkey: Optional[int] = pk _test: str FoxOrm.init_relations()
class OrmModelMeta(ModelMetaclass): if TYPE_CHECKING: __columns__: Dict[str, Column] __table__: Table __relations__: dict __tablename__: str __metadata__: MetaData __abstract__: bool __pkey_name__: str @property def pkey_column(cls): return getattr(cls.__table__.c, cls.__pkey_name__) @classmethod def _check_type(mcs, namespace: dict, key: str, expected_type: type): if key in namespace and not isinstance(namespace[key], expected_type): raise OrmException( f'{key} must be of type {expected_type.__qualname__}') @classmethod def _ensure_proper_init(mcs, namespace): if '__sqla_table__' in namespace: raise OrmException( 'You are using pre 0.3 model syntax. Check the docs for new instructions' ) if '__table__' in namespace: raise OrmException('__table__ should not be set') mcs._check_type(namespace, '__tablename__', str) mcs._check_type(namespace, '__metadata__', MetaData) mcs._check_type(namespace, '__abstract__', bool) def get_namespace(cls): namespace = {} namespace['__annotations__'] = annotations = {} for base in cls.mro(): if not (issubclass(base, OrmModel) and base is not OrmModel): break for column_name, namespace_value in base.__fields__.items(): namespace[column_name] = namespace_value for column_name, annotation in base.__annotations__.items(): annotations[column_name] = annotation return namespace def __getattribute__(cls, item): caller = traceback.extract_stack()[-2] if caller.name == 'validate_field_name' and caller.filename.endswith( f'{os.sep}pydantic{os.sep}utils.py'): return super().__getattribute__(item) if item.startswith('_'): return super().__getattribute__(item) if item in cls.__columns__: return getattr(cls.__table__.columns, item) if item in cls.__relations__: return cls.__relations__[item] return super().__getattribute__(item) def __new__(mcs, name, bases, namespace, **kwargs): if bases[0] == BaseModel: return super().__new__(mcs, name, bases, namespace, **kwargs) inherited_columns = {} for base in bases[::-1]: if issubclass(base, OrmModel) and base is not OrmModel: inherited_columns.update( {x.name: x.copy() for x in base.__columns__.values()}) mcs._ensure_proper_init(namespace) table_name = namespace.get('__tablename__', None) or camel_to_snake(name) metadata = namespace.get('__metadata__', None) or FoxOrm.metadata abstract = namespace.get('__abstract__', None) or False new_namespace = {} relation_namespace = {} for k, v in namespace.items(): if k == '__tablename__': continue if isinstance(v, _GenericIterableRelation): relation_namespace[k] = v else: new_namespace[k] = v new_namespace['__annotations__'] = annotations = {} for k, v in namespace.get('__annotations__', {}).items(): if k in relation_namespace: continue annotations[k] = v columns = {} for column_name, namespace_value in new_namespace.items(): if not is_valid_column_name( column_name) or not is_valid_column_value(namespace_value): continue if column_name not in annotations: raise OrmException(f'Unannotated field {column_name}') column, value = construct_column(column_name, annotations[column_name], namespace_value) columns[column.name] = column new_namespace[column_name] = value for column_name, annotation in annotations.items(): if not is_valid_column_name(column_name): continue if column_name not in columns: column, _ = construct_column(column_name, annotation, tuple()) columns[column.name] = column all_columns = inherited_columns.copy() all_columns.update(columns) # Hack for generating FastAPI models if (len(bases) == 1 and issubclass((base := bases[0]), OrmModel) and base is not OrmModel and base.__name__ == name): stack = traceback.extract_stack() if len(stack) >= 3: caller = stack[-3] if caller.name == 'create_cloned_field' and caller.filename.endswith( f'{os.sep}fastapi{os.sep}utils.py'): return ModelMetaclass(name, (BaseModel, ), base.get_namespace()) new_namespace['__abstract__'] = abstract new_namespace['__columns__'] = all_columns if abstract: new_namespace['__pkey_name__'] = None new_namespace['__table__'] = None new_namespace['c'] = None else: if sum([x.primary_key for x in all_columns.values()]) != 1: raise OrmException('Model should have exactly one primary key') new_namespace['__pkey_name__'] = [ x.name for x in all_columns.values() if x.primary_key ][0] new_namespace['__table__'] = table = Table(table_name, metadata, *all_columns.values()) new_namespace['c'] = table.c new_namespace['__relations__'] = relation_namespace cls = super().__new__(mcs, name, bases, new_namespace, **kwargs) if not abstract: for rel in relation_namespace.values(): FoxOrm._lazyinit_relation(metadata, rel, cls) return cls
def setUpClass(cls): if os.path.exists(DB_FILE): os.remove(DB_FILE) FoxOrm.init(DB_URI) cls.engine = create_engine(DB_URI) FoxOrm.metadata.create_all(cls.engine)
async def connect(): if os.path.exists(DB_FILE): os.remove(DB_FILE) FoxOrm.metadata.create_all(create_engine(DB_URI)) FoxOrm.init(DB_URI) await FoxOrm.connect()