SqlAlchemy avoid cyclic dependencies in ORM without using strings - python

Is there a way to be more explicit in SqlAlchemy ORM definitions by using class/member references instead of string constants without running into cyclic dependencies? One of the main benefits of an ORM is keeping things 'cleaner' and more maintainable than having string constants copied all over the place. This totally undermines that benefit.
A simple example from SqlAlchemy's docs, showing using string constants.
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey('parent.id'))
I want to do this:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship(Child)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Parent.id))
This is generally legal, but the problem is that I run into cyclic dependencies with the necessary importing of Parent from Child and of Child from Parent (assuming they are in separate files). The best I can do is split the difference - and use strings on one end and do the explicit class w/import on the other end. Just feels icky.
Just wondering if I'm missing something or someone has some ways of accomplishing this.

As an alternative to string-based attributes, attributes may also be defined after all classes have been created. Just add them to the target class after the fact:
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent_id = Column(Integer, ForeignKey(Parent.id))
Parent.childen = relationship(Child)

Related

How to insert related object only if not exists via SQLAlchemy ORM Relationship?

i'm a SQLAlchemy begginer ok?
Consider a have a on-to-may relatioship like
class Parent(Base):
__tablename__ = "parent_table"
id = Column(Integer, primary_key=True)
children = relationship("Child")
class Child(Base):
__tablename__ = "child_table"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
parent_id = Column(Integer, ForeignKey("parent_table.id"))
I'm using repositories classes to control insert, finds and deletes for this Entities.
When a do a Parent insert, it insert all Child related objects automatically, but if there is a Child object with the same name it will raises a Integrity Error.
How do i set Child to insert only when it not exists?
I need to do this in a way that i call ParentRepository object do not extrapolate your responsibilities.
I try to put this behavior on the Child Repository class but, it doen't works when i try via Parent Repository because it's doents change default cascade insert behavior.

back_populates behaviour in Django models

As explained in the answer of When do I need to use sqlalchemy back_populates? question, in SQLAlchemy you can define a related field in both of the classes to be explicit by using the parameter back_populates referring to the other variable name instead of the Django way of defining in one side and implicitly be defined in the other end by using the related_name or the default value <classname>_set
I like that way because it follow the principle of the Python Zen better explicit than implicit.
The question is how to write this code
class Parent(Base):
__tablename__ = 'parent'
id = Column(Integer, primary_key=True)
children = relationship("Child", back_populates="parent") # Parent.children <--o2o--> Child.parent
class Child(Base):
__tablename__ = 'child'
id = Column(Integer, primary_key=True)
parent = relationship("Parent", back_populates="children") # Child.parent <--o2o--> Parent.children
in Django >= 3.

Python Sqlalchemy - tablename as a variable

I am using SQLAlchemy in Python and am declaring my classes inheriting from a declarative base as follows:
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class SomeClass(Base):
__tablename__ = 'some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
As a user I would like to define the __tablename__ as a parameter, and not a hard-coded value , something like this:
class SomeClass(Base):
__tablename__ = f'{environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
It is my understanding that f'{environment}_some_table' will be be evaluated when I import this package, and therefore I won't be able to modify it at a later stage (i.e. in an interactive notebook). I have a broken piece of code that tries to solve this through nested classes and encapsulation, but I do not manage to reference the instance variable of an outer class.
class Outer:
def __init__(self, env):
self.environment = env
class SomeClass(Base):
__tablename__ = f'{self.environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
I have read in a couple of SO questions that it is better not to use nested classes since no special relationship is established between these classes.
So what kind of design should I use to solve this problem?
Thanks in advance!
you can make all your model definitions inside a function scope so the will be depended on outer arguments:
def create_models(environment):
class SomeClass(Base):
__tablename__ = f'{environment}_some_table'
id = Column(Integer, primary_key=True)
name = Column(String(50))
...
globals().update(locals()) # update outer scope if needed
... # some time later
create_models('cheese')
... # now create the db session/engine/etc ...
another choice to look at is the builtin reload method. check it out :)

Python 3 and sqlachemy model's properties

I have two declarative sqlalchemy models.
class Users(Base):
__tablename__ = 'Users'
ID = Column(INTEGER, primary_key=True)
_Activities = relationship('Activities', lazy='subquery')
class UserCourseActivities(Base):
__tablename__ = 'Activities'
ActivityID = Column(INTEGER, primary_key=True)
UserID = Column(INTEGER, ForeignKey('Users.ID'))
ActivityCount = Column(INTEGER)
Is there a way to have each instance of Users have a total (activity count) in their __dict__? I've tried adding other class attributes, but I fear I might have to use classical mappings. The Users table has a lot of relations that make the declarative method much more attractive. Is there any way to accomplish this?
Can I use the #column_property decorator? I have no idea how to actually use it though.
Turns out that column property isn't a decorator.
activity_total = column_property(
select(
[func.sum(
Activities.ActivityCount
)
]).\
where(Activities.UserID==PK1).\
correlate_except(Activities)
) #This is officially the ugliest thing I have ever seen
This 'column' shows up in the User instances __dict__ too.

How to specify relations using SQLAlchemy declarative syntax?

I can't find any proper documentation on how to specify relations
using the declarative syntax of SQLAlchemy.. Is it unsupported? That is, should I use the "traditional" syntax?
I am looking for a way to specify relations at a higher level, avoiding having to mess with foreign keys etc.. I'd like to just declare "addresses = OneToMany(Address)" and let the framework handle the details.. I know that Elixir can do that, but I was wondering if "plain" SQLA could do it too.
Thanks for your help!
Assuming you are referring to the declarative plugin, where everything I am about to say is documented with examples:
class User(Base):
__tablename__ = 'users'
id = Column('id', Integer, primary_key=True)
addresses = relation("Address", backref="user")
class Address(Base):
__tablename__ = 'addresses'
id = Column('id', Integer, primary_key=True)
user_id = Column('user_id', Integer, ForeignKey('users.id'))
Look at the "Configuring Relations" section of the Declarative docs. Not quite as high level as "OneToMany" but better than fully specifying the relation.
class Address(Base):
__tablename__ = 'addresses'
id = Column(Integer, primary_key=True)
email = Column(String(50))
user_id = Column(Integer, ForeignKey('users.id'))

Categories