On Thu, Aug 6, 2020, at 5:11 PM, Brendan Blanchard wrote: > I think I've misunderstood the usage of extend_existing=True on a table, but > from my reading today it sounds like it will only have an effect on building > up a table definition prior to calling create_all() on the metadata?
it's really only used when you make use of the autoload_with / autoload flag on Table. > > Forgetting that, currently, the application wouldn't necessarily create the > Collection classes ahead of creating any Member-like objects, thus the > original model and table creation won't have the extra FK columns assigned > during the creation of the Collection classes. This was developed with the > idea that raw data would be loaded into the database (as Member-like > objects), and Collections of them would later be added (such as 1-Hour > Collections that contain all 5-second data during that hour as members) that > can then run statistics on their member data. It sounds like my simplest > solution will be to make sure the Collection classes are instantiated ahead > of any objects being created (or, if more are added later, modify the DB > accordingly)? yeah that would be fine, the other way to approach it is to use more of a dynamic table format but that is not as easy to query. it sounds like you are building a star schema? https://en.wikipedia.org/wiki/Star_schema > > On Thu, Aug 6, 2020 at 4:59 PM Mike Bayer <mike...@zzzcomputing.com> wrote: >> __ >> >> >> On Thu, Aug 6, 2020, at 3:45 PM, Brendan Blanchard wrote: >>> I'm now having a problem with the actual implementation of this beyond the >>> test case I provided. My database already exists (was created previously >>> with Member-like SQLA objects), so the tables are defined already, but the >>> models that created them have extend_existing=True in their __table_args__. >> >> why extend_existing? are you also using table reflection? that might be a >> little ambitious here. there's no other reason to use that flag and your >> test case as written it doesn't seem to be needed. >> >> >> >>> >>> However, when I create my Collection classes dynamically (which add the FK >>> column and relationship to the Member-like objects), the attributes are >>> assigned dynamically as expected (the relationships and FKs exist on the >>> Member-like classes), but I am getting sqlite3.OperationalError: no such >>> column: met.blvmetstats1hour_id (see full trace below). >> >> that is correct, all columns need to be represented in the database >> naturally, I had assumed you were creating your schema based on the >> completed model after all attributes were added. >> >> >>> >>> It's my cursory understanding that my example (with Mike's fix) works >>> because my classes are not instantiated, nor are the tables created prior >>> to creating the metadata for the first time. If this is correct, what's >>> required in order for the new instrumentedattributes that were dynamically >>> added to the class to get added to the existing Member-like tables? >> >> SQLite supports the addition of columns using ALTER TABLE: >> https://www.sqlite.org/lang_altertable.html >> >> you can emit this command directly or perhaps use a tool like Alembic, >> however it's not clear what the flow of operations is here. note that it >> is very unconventional to emit ALTER TABLE while an application is actually >> running and as a product of normal user interaction, not really very >> different from repairing a car while it's moving. >> >> Because this is SQLite and I assume this is a simple console or GUI >> application with a single user / thread, maybe you can get away with it >> here, but note that these columns cannot be removed from the table once >> added, unless you copy the data into a new table and drop the old one. >> >> >>> >>> Thanks, >>> Brendan >>> >>>> 2020-08-06 15:37:42,007 -INFO- Running PeriodProcessor for >>>> BLVMetStats1Hour on BLVMET >>>> Traceback (most recent call last): >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1283, in _execute_context >>>> self.dialect.do_execute( >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", >>>> line 590, in do_execute >>>> cursor.execute(statement, parameters) >>>> sqlite3.OperationalError: no such column: met.blvmetstats1hour_id >>>> >>>> The above exception was the direct cause of the following exception: >>>> >>>> Traceback (most recent call last): >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/runtime/BLV/periods.py", >>>> line 36, in <module> >>>> proc(datetime(2020, 6, 1), datetime(2020, 8, 1), session=session, >>>> logger=logger) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/processing/periods/processors.py", >>>> line 25, in period_processor >>>> members = (session.query(member_cls) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", >>>> line 3319, in all >>>> return list(self) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", >>>> line 3481, in __iter__ >>>> return self._execute_and_instances(context) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/orm/query.py", >>>> line 3506, in _execute_and_instances >>>> result = conn.execute(querycontext.statement, self._params) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1020, in execute >>>> return meth(self, multiparams, params) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/sql/elements.py", >>>> line 298, in _execute_on_connection >>>> return connection._execute_clauseelement(self, multiparams, params) >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1133, in _execute_clauseelement >>>> ret = self._execute_context( >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1323, in _execute_context >>>> self._handle_dbapi_exception( >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1517, in _handle_dbapi_exception >>>> util.raise_( >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/util/compat.py", >>>> line 178, in raise_ >>>> raise exception >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/base.py", >>>> line 1283, in _execute_context >>>> self.dialect.do_execute( >>>> File >>>> "/home/brendan/PycharmProjects/BoulderAIRAnalysis/venv/lib/python3.8/site-packages/sqlalchemy/engine/default.py", >>>> line 590, in do_execute >>>> cursor.execute(statement, parameters) >>>> sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such >>>> column: met.blvmetstats1hour_id >>>> [SQL: SELECT met.date AS met_date, met.wind_speed AS met_wind_speed, >>>> met.wind_direction AS met_wind_direction, met."temp" AS met_temp, >>>> met.battery_voltage AS met_battery_voltage, met.air_temp AS met_air_temp, >>>> met.pmt_temp AS met_pmt_temp, met.solar_watt_average AS >>>> met_solar_watt_average, met.solar_kj_total AS met_solar_kj_total, >>>> met.relative_humidity AS met_relative_humidity, met.id AS met_id, met.site >>>> AS met_site, met.type AS met_type, met.blvmetstats1hour_id AS >>>> met_blvmetstats1hour_id, met.blvmetstats1month_id AS >>>> met_blvmetstats1month_id >>>> FROM met >>>> WHERE met.date >= ? AND met.date < ? AND met.type IN (?)] >>>> [parameters: ('2020-06-01 00:00:00.000000', '2020-06-01 01:00:00.000000', >>>> 'BLVMET')] >>>> (Background on this error at: http://sqlalche.me/e/e3q8) >>> >>> >>> >>> >>> >>> On Saturday, August 1, 2020 at 9:03:46 PM UTC-4, Brendan Blanchard wrote: >>>> Of course! Thanks for the quick reply, I was afraid/hoping it would be >>>> something simple I was missing -- I wasn't aware that it was required on >>>> both sides of a bi-directional relationship, but that makes sense. I'm >>>> using 1.3.17 and it does the same, I just only copied the first part of >>>> the traceback, assuming it was the root cause. Not to mention I was blind >>>> to what is was saying since I thought it was covered by having it on one >>>> side. Two lessons learned today! >>>> >>>> Thanks again, >>>> Brendan >>>> >>>> On Saturday, August 1, 2020 at 7:15:44 PM UTC-4, Mike Bayer wrote: >>>>> hi and thanks for the straightforward test case. >>>>> >>>>> I'm not sure if that's an old version of SQLAlchemy you're using, when I >>>>> run with current 1.3.18 release the error message is more descriptive: >>>>> >>>>> "Could not determine join condition between parent/child tables on >>>>> relationship ACollection.members - there are multiple foreign key paths >>>>> linking the tables. Specify the 'foreign_keys' argument, providing a >>>>> list of those columns which should be counted as containing a foreign key >>>>> reference to the parent table." >>>>> >>>>> the error message using a modern version should lead you right to the >>>>> issue which is that you forgot to put "foreign_keys" on the "members" >>>>> relationship: >>>>> >>>>> setattr( >>>>> collection_class, >>>>> "members", >>>>> relationship( >>>>> member_cls.__name__, >>>>> back_populates=f"{cls_name}Rel", >>>>> foreign_keys=f"[{member_cls.__name__}.{fk_col}]", >>>>> ), >>>>> ) >>>>> >>>>> >>>>> >>>>> >>>>> >>>>> On Sat, Aug 1, 2020, at 1:54 PM, Brendan Blanchard wrote: >>>>>> 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 sqlal...@googlegroups.com. >>>>>> To view this discussion on the web visit >>>>>> https://groups.google.com/d/msgid/sqlalchemy/a2dc5325-aa51-45f7-8d8d-388241bb84ceo%40googlegroups.com >>>>>> >>>>>> <https://groups.google.com/d/msgid/sqlalchemy/a2dc5325-aa51-45f7-8d8d-388241bb84ceo%40googlegroups.com?utm_medium=email&utm_source=footer>. >>>>> >>> >>> -- >>> 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 view this discussion on the web visit >>> https://groups.google.com/d/msgid/sqlalchemy/1c18624d-4403-42ea-9027-d69469a8dfbdo%40googlegroups.com >>> >>> <https://groups.google.com/d/msgid/sqlalchemy/1c18624d-4403-42ea-9027-d69469a8dfbdo%40googlegroups.com?utm_medium=email&utm_source=footer>. >> >> >> -- >> 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 a topic in the >> Google Groups "sqlalchemy" group. >> To unsubscribe from this topic, visit >> https://groups.google.com/d/topic/sqlalchemy/vTiuvBl1o00/unsubscribe. >> To unsubscribe from this group and all its topics, send an email to >> sqlalchemy+unsubscr...@googlegroups.com. >> To view this discussion on the web visit >> https://groups.google.com/d/msgid/sqlalchemy/c53ff2ce-1186-48f1-bffd-6ac71b02146a%40www.fastmail.com >> >> <https://groups.google.com/d/msgid/sqlalchemy/c53ff2ce-1186-48f1-bffd-6ac71b02146a%40www.fastmail.com?utm_medium=email&utm_source=footer>. > > -- > 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 view this discussion on the web visit > https://groups.google.com/d/msgid/sqlalchemy/CAFA3B43vd6KMsVA8fuUvGuVjfxabEwzTJ%3DMogS_qxdDt278QrA%40mail.gmail.com > > <https://groups.google.com/d/msgid/sqlalchemy/CAFA3B43vd6KMsVA8fuUvGuVjfxabEwzTJ%3DMogS_qxdDt278QrA%40mail.gmail.com?utm_medium=email&utm_source=footer>. -- 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 view this discussion on the web visit https://groups.google.com/d/msgid/sqlalchemy/f01211c2-292b-4951-9561-78dc54d66095%40www.fastmail.com.