On Sat, Nov 25, 2017 at 9:04 AM, Olaf <bastien...@gmail.com> wrote: > Hello, > > I tried to implement the table_per_related.py solution in my code but I > face new problems related to metaclasses. >
I haven't looked deeply yet but at first this looks like an overuse of metaclasses. The reason metaclasses are often useful (at least in my experience) is that they give you a "hook" that fires off when a class is first set up. With SQLAlchemy ORM mappings, you have other, simpler ways of intercepting this event, most directly the 'instrument_class" event: Base = declarative_base() @event.listens_for(Base, "instrument_class", propagate=True)def on_new_class(mapper, cls_): " ... " the docstring here is at http://docs.sqlalchemy.org/en/latest/orm/events.html?highlight=instrument_class#sqlalchemy.orm.events.InstanceEvents.init, but note you can apply this event to any Base, arbitrary mixin class or whatever you want, as long as you add propagate=True it will take place automatically for all inheriting classes. Similarly, intercepting the __init__ of an existing class is very simple using the "init" event: http://docs.sqlalchemy.org/en/latest/orm/events.html?highlight=instrument_class#sqlalchemy.orm.events.InstanceEvents.init where again, you can apply this to a Base or any mixin or whatever you want with propagate=True in order for it to be applied automatically to all inheriting classes. If you can do away with having to create additional metaclasses, you avoid a lot of the complexity in getting those all to work. > > Here is a schema to help you to understand the code : > > > <https://lh3.googleusercontent.com/-NesWeuhrl1M/Whl4BN4xHMI/AAAAAAAAAPU/tU_Pd_T03cwsWHtamsb8GY7PjkLgotScwCLcBGAs/s1600/schema_animals%2526cars.png> > > > My animal and car classes have to be inherited from a generic class called > "Stockable" which use a metaclass called "MetaBase". In addition to this, > my classes Animal and Car must also be inherited from the Base class of > SQLAlchemy. > > Now, I would like to be able to do this kind of things in order to map the > "Description" attribute of these classes : > > class Animal(Stockable, Base, metaclass=DeclDescription) > > or > > class Animal(Stockable, Base, HasDescription) > > I tried several things but until now, impossible to make all these classes > work together... > > Here is the code : > > from sqlalchemy import create_engine > from sqlalchemy.orm import sessionmaker > from sqlalchemy.ext.declarative import as_declarative, declared_attr > from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta > from sqlalchemy import Column, Integer, String, ForeignKey > from sqlalchemy.orm import relationship > > > class MetaBase(type): > > > def __init__(cls, nom, bases, contenu): > type.__init__(cls, nom, bases, contenu) > print("GenericMetaClass - __init__") > > > class DeclMetaBase(MetaBase, DeclarativeMeta): > > > def __init__(cls, nom, bases, contenu): > MetaBase.__init__(cls, nom, bases, contenu) > DeclarativeMeta.__init__(cls, nom, bases, contenu) > > > @as_declarative() > class Base(metaclass=DeclMetaBase): > """Base class which provides automated table name > and surrogate primary key column. > > > """ > @declared_attr > def __tablename__(cls): > return cls.__name__.lower() > id = Column(Integer, primary_key=True) > > > class Stockable(metaclass=MetaBase): > > > def __init__(self): > print("Stockable - __init__") > > > class Description(Stockable): > > > name = Column(String) > > > def __init__(self, name): > self.name = name > self.parent = None > > > def __repr__(self): > return self.name > > > class HasDescription(object): > > > @declared_attr > def descriptions(cls): > cls.Description = type( > "%sDescription" % cls.__name__, > (Description, Base,), > dict( > __tablename__="%s_descriptions" % > cls.__tablename__, > parent_id=Column(Integer, > ForeignKey("%s.id" % cls.__tablename__)), > parent=relationship(cls) > ) > ) > return relationship(cls.Description) > > > class DeclDescription(DeclMetaBase, HasDescription): > > > def __init__(cls, nom, bases, contenu): > DeclMetaBase.__init__(cls, nom, bases, contenu) > > > class Animal(Stockable, Base, metaclass=DeclDescription): > > > tablename = "animals" > id = Column(Integer, primary_key=True) > name = Column(String) > > > def __init__(self, name): > self.name = name > > > def __repr__(self): > return self.name > > > class Car(Stockable, Base, metaclass=DeclDescription): > > > tablename = "cars" > id = Column(Integer, primary_key=True) > name = Column(String) > > > def __init__(self, name): > self.name = name > > > def __repr__(self): > return self.name > > > engine = create_engine('sqlite://', echo=True) > > > Base.metadata.create_all(engine) > > > Session = sessionmaker(bind=engine) > session = Session() > > > rex = Animal("Rex") > swift = Car("Suzuki Swift") > > > d1 = Description("Rex is a good dog.") > d2 = Description("What a beautiful car !") > > > rex.description = d1 > d1.parent = rex > swift.description = d2 > d2.parent = swift > > > session.add(rex) > session.add(swift) > > > session.commit() > > > And the error : > > Traceback (most recent call last): > File "C:\Users\user\Desktop\sa_tests\Generic > Associations\description.py", line 21, in <module> > class Base(metaclass=DeclMetaBase): > File "C:\Users\user\Desktop\sa_tests\Generic > Associations\description.py", line 18, in _init_ > DeclarativeMeta.__init__(cls, nom, bases, contenu) > File "C:\Python34\lib\site-packages\sqlalchemy\ext\declarative\api.py", > line 64, in _init_ > as_declarative(cls, classname, cls.__dict_) > File "C:\Python34\lib\site-packages\sqlalchemy\ext\declarative\base.py", > line 88, in _as_declarative > MapperConfig.setup_mapping(cls, classname, dict) > File "C:\Python34\lib\site-packages\sqlalchemy\ext\declarative\base.py", > line 103, in setup_mapping > cfg_cls(cls, classname, dict) > File "C:\Python34\lib\site-packages\sqlalchemy\ext\declarative\base.py", > line 125, in _init_ > clsregistry.add_class(self.classname, self.cls) > File "C:\Python34\lib\site-packages\sqlalchemy\ext\ > declarative\clsregistry.py", line 34, in add_class > if classname in cls._decl_class_registry: > AttributeError: type object 'Base' has no attribute '_decl_class_registry' > > > Does someone have an idea ? > > Thank you ! > > Olaf > > -- > SQLAlchemy - > The Python SQL Toolkit and Object Relational Mapper > > http://www.sqlalchemy.org/ > > To post example code, please provide an MCVE: Minimal, Complete, and > Verifiable Example. See http://stackoverflow.com/help/mcve for a full > description. > --- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at https://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. > -- SQLAlchemy - The Python SQL Toolkit and Object Relational Mapper http://www.sqlalchemy.org/ To post example code, please provide an MCVE: Minimal, Complete, and Verifiable Example. See http://stackoverflow.com/help/mcve for a full description. --- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at https://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.