I have SQLA classes in a project that have date and data components and I'm 
attempting to create a single base-class that I can subclass to create 
statistics for all points of data during some interval, but I want the 
interval subclasses to maintain references to their sub-data such that if I 
change the underlying data of a monthly period, the statistics on that 
month can be recalculated (all of this behavior is left out, except the 
relationships). In my real use-case, the Member class could be one of 
dozens of SQLA classes, each of which could have multiple collection 
classes related to it, which is why dynamic creation using type( ) is 
necessary.

In a basic example, I have some data class that needs to be related to an 
arbitrary number of other collection classes (created dynamically). One 
created class might aggregate those data in 30-minute intervals, and 
another in monthly intervals. To do this, my thought was to create one base 
class that, when subclassed, is assigned a unique relationship to the 
member class. This is what I believe I'm doing, but I am running into 
sqlalchemy.exc.AmbiguousForeignKeysError. I've looked around extensively, 
and am reaching out here because my understanding is I'm doing what's 
required to have multiple relationships between two tables, but am still 
getting the error. I'm aware it could be an issue with how I'm creating 
subclasses as well, but haven't had any luck using inherit_conditions, 
either. The output and trimmed-down example is below:

Setting ACollection_id on Member with ForeignKey(collections.id)
> Setting "members" attribute on ACollection which back populates 
> "ACollectionRel"
> Setting attribute "ACollectionRel" on Member with: 
>     foreign_keys=[Member.ACollection_id]
>
> Setting BCollection_id on Member with ForeignKey(collections.id)
> Setting "members" attribute on BCollection which back populates 
> "BCollectionRel"
> Setting attribute "BCollectionRel" on Member with: 
>     foreign_keys=[Member.BCollection_id]
>

Traceback (most recent call last):
>   File 
> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.7/site-packages/sqlalchemy/orm/relationships.py",
>  
> line 2620, in _determine_joins
>     consider_as_foreign_keys=consider_as_foreign_keys,
>   File "<string>", line 2, in join_condition
>   File "<string>", line 2, in _join_condition
>   File 
> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.7/site-packages/sqlalchemy/util/deprecations.py",
>  
> line 139, in warned
>     return fn(*args, **kwargs)
>   File 
> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.7/site-packages/sqlalchemy/sql/selectable.py",
>  
> line 967, in _join_condition
>     a, b, constraints, consider_as_foreign_keys
>   File 
> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.7/site-packages/sqlalchemy/sql/selectable.py",
>  
> line 1084, in _joincond_trim_constraints
>     "join explicitly." % (a.description, b.description)
> sqlalchemy.exc.AmbiguousForeignKeysError: Can't determine join between 
> 'collections' and 'basic_members'; tables have more than one foreign key 
> constraint relationship between them. Please specify the 'onclause' of this 
> join explicitly. 
>

from sqlalchemy import Column, String, Integer, DateTime, UniqueConstraint, 
ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine

Base = declarative_base()


class Member(Base):
    """A testable basic SQLA class that contains some floating-point 
information."""
    __tablename__ = 'basic_members'

    id = Column(Integer, primary_key=True)


class CollectionBase(Base):
    id = Column(Integer, primary_key=True)
    date = Column(DateTime)
    type = Column(String(15))

    __tablename__ = 'collections'
    __table_args__ = (
        UniqueConstraint('date', 'type'),
        {'extend_existing': True}
    )
    __mapper_args__ = {
        'polymorphic_on': 'type',
        'polymorphic_identity': 'base_class',
    }


def collection_class_factory(name, member_cls):

    all_attrs = {
        '__mapper_args__': {
            'polymorphic_identity': name
        },

        'member_class': member_cls,  # keep a reference to the member class on 
the 'collection' class
    }

    cls_name = f'{name}Collection'
    fk_col = f'{cls_name}_id'

    collection_class = type(cls_name, (CollectionBase,), all_attrs)

    print(f'Setting {fk_col} on {member_cls.__name__} with 
ForeignKey({collection_class.__tablename__}.id)')
    # set the foreign key col on the members class to reference the periods 
they belong to
    setattr(member_cls, fk_col, Column(Integer, 
ForeignKey(f'{collection_class.__tablename__}.id')))

    print(f'Setting "members" attribute on {collection_class.__name__} which 
back populates "{cls_name}Rel"')
    # set the relationship from periods -> members
    setattr(collection_class, 'members', relationship(member_cls.__name__, 
back_populates=f'{cls_name}Rel'))

    print(f'Setting attribute "{cls_name}Rel" on {member_cls.__name__} with: ')
    print(f'\tforeign_keys=[{member_cls.__name__}.{fk_col}]')
    # set the relationship from members -> periods
    setattr(member_cls, f'{cls_name}Rel', relationship(
        collection_class.__name__, uselist=False,
        foreign_keys=f'[{member_cls.__name__}.{fk_col}]',
        back_populates='members'
    ))
    print()

    return collection_class


CollectionA = collection_class_factory('A', Member)
CollectionB = collection_class_factory('B', Member)

engine = create_engine('sqlite://')
session = sessionmaker(bind=engine)()

Base.metadata.create_all(bind=engine)

a = CollectionA()
b = CollectionB()

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/sqlalchemy/a2dc5325-aa51-45f7-8d8d-388241bb84ceo%40googlegroups.com.

Reply via email to