Nested Marshmallow fields and Sqlalchemy relationships - python

In my sqlalchemy classes I have the following classes:
class FooBar(Model):
__tablename__ = ‘foobar’
id = Column('id', Integer, primary_key=True)
foonr = Column('foonr', Integer, ForeignKey('foo.nr'), nullable=False)
barnr = Column('barnr', String, ForeignKey('bar.nr'), nullable=False)
class Foo(Model):
__tablename__ = ‘foo’
nr = Column('nr', Integer, primary_key=True)
foo_name = Column(‘name’,String)
class Bar(Model):
__tablename__ = ‘bar’
nr = Column('nr', Integer, primary_key=True)
bar_name = Column(‘name’,String)
foo_bar = relationship('foobar', uselist=False)
When I try to nest the classes Foo or Bar in a Marshmallow Schema for FooBar I’m not getting any results (the dictionaries don't have any references to the classes Foo or Bar).
class FooBarSchema(Schema):
id = fields.Int()
foo = fields.Nested('FooSchema', many=False)
bar = fields.Nested('BarSchema', many=False)
How can I get the Foo and Bar classes in the results of the FooBarSchema?

Ok... I'll give you the solution to your problem.
class FooBar(Model):
__tablename__ = 'foobar'
id = Column('id', Integer, primary_key=True)
foonr = Column('foonr', Integer, ForeignKey('foo.nr'), nullable=False)
barnr = Column('barnr', String, ForeignKey('bar.nr'), nullable=False)
foo = relationship("Foo", uselist=False)
bar = relationship("Bar", uselist=False)
class FooBarSchema(Schema):
id = fields.Int()
foo = fields.Nested('FooSchema', many=False)
bar = fields.Nested('BarSchema', many=False)
But analyzing your code I think we can make it more pythonic.
If, and only if, you do not have extra data in the association table, we can change some things.
Looking at SQLAlchemy doc's for an Many To Many relationship, we can to use the secondary parameter of the relationship().
We have to keep class as you currently have and the class Bar like that:
class Bar(Model):
__tablename__ = 'bar'
nr = Column('nr', Integer, primary_key=True)
bar_name = Column('name',String)
foos = relationship("Foo", secondary="foobar", backref="bars")
So in Bar.foos we have a list of Foo objects, and the backref also makes it possible to have an Bar list in Foo.bars.
Now we have to configure the BarSchema and FooSchema classes.
class FooSchema(Schema):
nr = fields.Int()
foo_name = fields.Str()
bars = fields.Nested('BarSchema', exclude=('foos',), many=True)
class BarSchema(Schema):
nr = fields.Int()
bar_name = fields.Str()
foos = fields.Nested('FooSchema', exclude=('bars',), many=True)
The exclude is to avoid recursive problems.

Related

How to assign a custom SQL query which returns a collection of Rows as an attribute of an ORM model

For example, suppose I have three models: Book, Author, and BookAuthor where a book can have many authors and an author can have many books.
class BookAuthor(Base):
__tablename__ = 'book_authors'
author_id = Column(ForeignKey('authors.id'), primary_key=True)
book_id = Column(ForeignKey('books.id'), primary_key=True)
blurb = Column(String(50))
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
I would like to create an authors attribute of Book which returns every author for the book and the corresponding blurb about each author. Something like this
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
#authors.expression
def authors(cls):
strSQL = "my custom SQL query"
return execute(strSQL)
Demo
from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, Session
# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)
# Make the DeclarativeMeta
Base = declarative_base()
class BookAuthor(Base):
__tablename__ = 'book_authors'
author_id = Column(ForeignKey('authors.id'), primary_key=True)
book_id = Column(ForeignKey('books.id'), primary_key=True)
blurb = Column(String(50))
class Author(Base):
__tablename__ = 'authors'
id = Column(Integer, primary_key=True)
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
# Create the tables in the database
Base.metadata.create_all(engine)
# Make data
with Session(bind=engine) as session:
# add parents
a1 = Author()
session.add(a1)
a2 = Author()
session.add(a2)
session.commit()
# add children
b1 = Book()
session.add(b1)
b2 = Book()
session.add(b2)
session.commit()
# map books to authors
ba1 = BookAuthor(author_id=a1.id, book_id=b1.id, blurb='foo')
ba2 = BookAuthor(author_id=a1.id, book_id=b2.id, blurb='bar')
ba3 = BookAuthor(author_id=a2.id, book_id=b2.id, blurb='baz')
session.add(ba1)
session.add(ba2)
session.add(ba3)
session.commit()
# Get the authors for book with id 2
with Session(bind=engine) as session:
s = """
SELECT foo.* FROM (
SELECT
authors.*,
book_authors.blurb,
book_authors.book_id
FROM authors INNER JOIN book_authors ON authors.id = book_authors.author_id
) AS foo
INNER JOIN books ON foo.book_id = books.id
WHERE books.id = :bookid
"""
result = session.execute(s, params={'bookid':2}).fetchall()
print(result)
See that semi-nasty query at the end? It successfully returns the authors for book 2, including the corresponding blurb about each author. I would like to create a .authors attribute of my Book model that executes this query.
Figured it out. The trick was to key was to use a plain descriptor with object_session()
class Book(Base):
__tablename__ = 'books'
id = Column(Integer, primary_key=True)
#property
def authors(self):
s = """
SELECT foo.* FROM (
SELECT
authors.*,
book_authors.blurb,
book_authors.book_id
FROM authors INNER JOIN book_authors ON authors.id = book_authors.author_id
) AS foo
INNER JOIN books ON foo.book_id = books.id
WHERE books.id = :bookid
"""
result = object_session(self).execute(s, params={'bookid': self.id}).fetchall()
return result

How can I access subclasses from upper class in sqlalchemy?

I have 3 classes;
'Company' top class its subclass 'Department' its subclass 'DepartmentalUnit'
I can access the values ​​of all classes from the 'DepartmentalUnit' class to the top class 'Company'
What I could not do and understand despite reading the document is that;;
I cannot access other subclasses from the 'company' class
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company')
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department')
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
The code from which I access the upper classes from the subclasses:
query = session.query(DepartmentalUnit)
instance = query.all()
for i in instance:
print(i.department.company.name)
print(i.department.department_name)
print(i.departmental_unit_name)
The code I can't access other subclasses from the company class:
query = session.query(Company)
instance = query.all()
for i in instance:
print(i.department.department_name)
Your last query should be used differently:
there is a typo in the name of the relationship: should be departments instead of department
given that the relationship is 1-N, the result is a list, so you should iterate over children.
This should work:
query = session.query(Company)
for company in query.all():
print(company.name)
for dep in company.departments:
print(" ", dep.department_name)
for dep_unit in dep.departmentalunits:
print(" ", dep_unit.departmental_unit_name)
I solved the problem. I added a backref to relationships and now I can access all of them from the company. Not sure if it's a correct method? However, I am currently getting the return I want. I have no unanswered request yet.
Example solved:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
departments = relationship('Department',backref='company',uselist=False)
class Department(Base):
__tablename__ = 'department'
id = Column(Integer, primary_key=True)
department_name = Column(String)
company_id = Column(Integer, ForeignKey('company.id'))
departmentalunits = relationship('DepartmentalUnit', backref='department',uselist=False)
class DepartmentalUnit(Base):
__tablename__ = 'departmentalunit'
id = Column(Integer, primary_key=True,nullable=False)
departmental_unit_name = Column(String)
departments_id = Column(Integer, ForeignKey('department.id'))
query = session.query(Company)
instance = query.all()
for i in instance:
print(f"Company: {i.name}")
print(f"Department: {i.departments.department_name}")
print(f"Department Unit: {i.departments.departmentalunits.departmental_unit_name}")
print( f"Report Category : {i.departments.departmentalunits.reportcategoryoftheunit.report_category_name}")

SQLAlchemy - querying multiple tables and returning nested objects

Suppose we have a simple one-to-many relationship between Company and Employee, is there a way to query all companies and have a list of employees in the attribute of each company?
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
class Employee(Base):
__tablename__ = 'employee'
id = Column(Integer, primary_key=True)
first_name = Column(String)
last_name = Column(String)
company_id = Column(Integer, ForeignKey(Company.id))
I'm looking for something like this:
>>> result = db.session.query(Company).join(Employee).all()
>>> result[0].Employee
[<Employee object at 0x...>, <Employee object at 0x...>]
The size of result should be same as the number of rows in company table.
I tried the following and it gives tuple of objects (which makes sense) instead of nice parent / child structure:
>>> db.session.query(Company, Employee).filter(Company.id = Employee.company_id).all()
It's not hard to convert this into my desired object structure but just wanted to see if there's any shortcut.
You have to configure the relationship in the parent class:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = relationship('Employee', lazy='joined') # <<< Add this line
Then you can query it without a join:
companies = session.query(Company).all()
print(companies[0].employees)
Documentation:
https://docs.sqlalchemy.org/en/13/orm/loading_relationships.html
You could do something like this:
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True)
name = Column(String)
employees = db.session.query(Company, Employee).filter(Company.id = self.id).all()
self.employee_list = ['{0} {1}'.format(c.first_name, c.last_name) for c in employees]
Then you could access a employee name with Company.employee_list[0]

SQLAlchemy - How can I make eager loading count property

I want to make a property for model which contains count.
Since I always need the property, I want to make query with JOIN like sqlalchemy.orm.relationship with lazy='joined'
For example, I defined models like following
import sqlalchemy as s, func
from sqlalchemy.orm import relatioship
# ...
class Foo(Base):
__tablename__ = 'foo'
id = s.Column(s.Integer, primary_key=True)
bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = s.Column(s.Integer, primary_key=True)
#property
def foo_count(self):
return Foo.query.filter_by(bar=self).count()
When I access to property foo_count, then it will send query to DBMS.
Since I always access to this property, I want to load it eagerly the counting property like this
# Not session.query(Bar, func.count(Foo.id)).join(Foo) ...
bar = Bar.query.first()
SQL will be like this
SELECT id, COUNT(Foo.id)
FROM bar
INNER JOIN foo
ON bar.id = foo.id
Then bar.foo_count will not occur SQL query.
How can I make a property like foo_count?
I solved it by using sqlalchemy.orm.column_property
I replaced the foo_count by following
import sqlalchemy as s, func, select
from sqlalchemy.orm import relationship, column_property
# ...
class Foo(Base):
__tablename__ = 'foo'
id = s.Column(s.Integer, primary_key=True)
bar_id = s.Column(s.Integer, s.ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = s.Column(s.Integer, primary_key=True)
foo_count = column_property(
select([func.count(Foo.id)])
.where(Foo.bar_id == id)
)
Please take a look at the Hybrid Attribute extension.
Your object model will look similar to the below:
class Foo(Base):
__tablename__ = 'foo'
id = Column(Integer, primary_key=True)
bar_id = Column(Integer, ForeignKey('bar.id'))
bar = relationship('Bar')
class Bar(Base):
__tablename__ = 'bar'
id = Column(Integer, primary_key=True)
#hybrid_property
def foo_count(self):
return object_session(self).query(Foo).filter(Foo.bar==self).count()
#foo_count.expression
def foo_count(cls):
return select([func.count(Foo.id)]).where(Foo.bar_id == cls.id).label('foo_count')
foo_count will not be eagerly loaded, but you can use it in queries like below (both in SELECT and in WHERE clause:
qry = session.query(Bar, Bar.foo_count).filter(Bar.foo_count > 0)
for (bar, bar_foo_count) in qry:
print bar, bar_foo_count
As you can see, the query will return tuples of (Bar, foo_count) in just one query, and now you can do what you wish with that.

From Elixir to SqlAlchemy Declarative, Polymorphic Nodes with Child Inheritance [duplicate]

This question already has an answer here:
Creating container relationship in declarative SQLAlchemy
(1 answer)
Closed 3 years ago.
I have already finished a good bit of my python/Elixir interface on my existing database. I am now considering to drop Elixir and move everything into pure SQLAlchemy, most likely wanting to use Declarative methods.
I am not sure where to even start with this particular inheritance relationship. I don't think sqlalchemy performs inheritance in this manner (or as "magically"), and I am a bit confused how the same would look in sqlalchemy:
This is a polymorphic multi-table join, with each class mapped to its own database table. When finished, another class (not included here) will have a OneToMany with 'Comp'. The Comp subclasses have a Primary Key that is a Foreign key to Comp.id.
class Comp(Entity):
using_options(inheritance='multi')
parent = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
quantity = Field(Numeric(4), default=1)
def __repr__(self):
return "<Comp>"
## If not familiar with Elixir, each of the following "refid" point to a different
## table depending on its class. This is the primary need for polymorphism.
class CompAssm(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Assembly', onupdate='cascade', ondelete='set null')
def __repr__(self):
return "<CompAssm>"
class CompItem(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Item', onupdate='cascade')
def __repr__(self):
return "<CompItem>"
class CompLabor(Comp):
using_options(inheritance='multi')
refid = ManyToOne('Labor', onupdate='cascade')
def __repr__(self):
return "<CompLabor>"
I think this is the general direction, but may still need tweaking.
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class Assembly(Base):
__tablename__ = 'assembly'
assm_id = Column(Integer, primary_key=True)
children = relationship('Comp')
### other assembly stuff
class Comp(Base):
__tablename__ = 'components'
id = Column(Integer, primary_key=True)
comp_type = Column('type', String(50))
__mapper_args__ = {'polymorphic_on': comp_type}
parent = Column(Integer, ForeignKey('assembly.assm_id'))
quantity = Column(Integer)
class CompAssm(Comp):
__tablename__ = 'compassm'
__mapper_args__ = {'polymorphic_identity': 'compassm'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('assembly.assm_id'))
class CompItem(Comp):
__tablename__ = 'compitem'
__mapper_args__ = {'polymorphic_identity': 'compitem'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('items.id'))
class CompLabor(Comp):
__tablename__ = 'complabor'
__mapper_args__ = {'polymorphic_identity': 'complabor'}
id = Column(Integer, ForeignKey('components.id'), primary_key=True)
refid = Column(Integer, ForeignKey('labors.id'))

Categories

Resources