FastAPI, Pydantic joinedload instead of lazy - python

I am trying to optimize on the performance of my models, however I can see that FastAPI is loading the relations of my model PurchaseOrderPart as lazy.. This is causing a n+1 problem, which is a big problem.
Is there any way I can make FastAPI load the relations as joined? When I am not using the response model parameter I get most of the data I need, but not all the relations.
class PurchaseOrderPart(Base):
__tablename__ = "purchase_order_part"
id = Column(BigInteger, primary_key=True, index=True)
quantity = Column(Float)
ordered_at = Column(DateTime, nullable=True)
expected_at = Column(DateTime, nullable=True)
confirmed_at = Column(DateTime, nullable=True)
delivered_at = Column(DateTime, nullable=True)
invoiced_at = Column(DateTime, nullable=True)
price_dkk = Column(BigInteger, server_default="0")
deviation = Column(String, nullable=True)
supplier_part_id = Column(BigInteger, ForeignKey("supplier_part.id"))
supplier_id = Column(BigInteger, ForeignKey("supplier.id"))
project_reference_id = Column(BigInteger, ForeignKey(
"project_reference.id"), nullable=True)
stockpile_part_id = Column(BigInteger, ForeignKey(
"stockpile_part.id"), nullable=True)
purchase_order_id = Column(BigInteger, ForeignKey(
"purchase_order.id"), nullable=True)
created_at = Column(DateTime, server_default=func.now())
updated_at = Column(DateTime, server_default=func.now(),
onupdate=func.current_timestamp())
supplier_part = relationship(
"SupplierPart", uselist=False, foreign_keys=[supplier_part_id], lazy="selectin")
supplier = relationship("Supplier", uselist=False,
foreign_keys=[supplier_id], lazy="selectin")
project_reference = relationship(
"ProjectReference", uselist=False, foreign_keys=[project_reference_id], lazy="selectin")
stockpile_part = relationship(
"StockpilePart", uselist=False, foreign_keys=[stockpile_part_id], lazy="selectin")
purchase_order = relationship(
"PurchaseOrder", uselist=False, foreign_keys=[purchase_order_id], lazy="selectin")
purchase_order_part_notes = relationship(
"PurchaseOrderPartNote", back_populates="purchase_order_part", lazy="selectin")`
class PurchaseOrderPartRelation(PurchaseOrderPart):
purchase_order_part_notes: Optional[List["PurchaseOrderPartNote"]] = None
supplier: Optional[SupplierBase] = None
supplier_part: Optional[SupplierPart] = None
purchase_order: Optional[PurchaseOrder] = None
project_reference: Optional[ProjectReference] = None
stockpile_part: Optional[StockpilePartRelation] = None
part: Optional["Part"]
#################################
# Not Confirmed
#################################
#router.get(
"/not_confirmed",
summary="List all purchase order parts that are ordered but not confirmed",
status_code=status.HTTP_200_OK,
)
def get_not_confirmed_parts(request: Request, end_days_from_now: int = 7,
filter_and: Optional[List[str]] = Query(None),
filter_or: Optional[List[str]] = Query(None),
sort: Optional[List[str]] = Query(None),
page: int = 1,
size: int = 1000,
current_user: User = Depends(
get_current_user),
db: Session = Depends(get_db)):
with tracer.start_span("traces for not confirmed parts") as current_span:
query = db.query(models.PurchaseOrderPart, models.SupplierPart, models.Part).\
filter(and_(
models.PurchaseOrderPart.confirmed_at.is_(None),
models.PurchaseOrderPart.delivered_at.is_(None),
models.PurchaseOrderPart.ordered_at <= ordered_date,
models.PurchaseOrderPart.supplier_part_id == models.SupplierPart.id,
models.Part.primary_supplier_part_id == models.SupplierPart.id
))
# logger.debug(str(query))
# return query
with tracer.start_span("get filtered page trace") as current_span:
results = get_filtered_page(
db,
query,
filter_or,
filter_and,
sort,
page,
size)
return results

Related

sqlalchemy.exc.ArgumentError: Can't find property named "consumed" on mapped class Instances->instance in this Query

I have this query
self.session.query(Instances).options(joinedload('consumed')).outerjoin(Consumed).filter(
and_(
queryInstances.customer_id == customer_id,
queryInstances.purged == 'false'
)
)
that returns the following error:
sqlalchemy.exc.ArgumentError: Can't find property named "consumed" on mapped class Instances->instance in this Query.
And my classes are
Instances:
class Instances(Base):
__tablename__ = 'instance'
id = Column(UUID, primary_key=True, server_default=FetchedValue())
coupon_id = Column(UUID, ForeignKey(Coupon.id), nullable=False)
customer_id = Column(TEXT, nullable=False)
created_at = Column(TIMESTAMP, nullable=False, server_default=FetchedValue())
reference = Column(TEXT, nullable=False)
offline_data = Column(TEXT, nullable=False)
purged = Column(BOOLEAN, nullable=False)
customer_info = Column(JSONB)
locked_for = Column(TEXT)
locked_until = Column(TIMESTAMP)
expires_at = Column(TIMESTAMP)
coupon = relationship(Coupon, lazy='joined')
and Consumed:
class Consumed(Base):
__tablename__ = 'consumed'
id = Column(UUID, primary_key=True, server_default=FetchedValue())
instance_id = Column(UUID, ForeignKey(Instances.id), nullable=False)
consumed_at = Column(TIMESTAMP, nullable=False, server_default=FetchedValue())
pos_id = Column(TEXT, nullable=False)
instance = relationship(Instances, backref=backref('consumed', uselist=False), lazy='joined')
Why I'm getting this error?
SQLAlchemy version 1.4

Sqlalchemy Relationship field return None

I have the following models:
class DictionaryItem(Base):
__tablename__ = 'dictionary_items'
id = Column(Integer, primary_key=True)
text = Column(String, nullable=True)
phonetic_symbols = Column(String, nullable=True)
fgner_entity = Column(String, nullable=True)
status = Column(String, default="waiting")
user_id = Column(Integer, ForeignKey('users.id'), nullable=False)
user = relationship("User", back_populates="dictionary_items")
data_type_id = Column(Integer, ForeignKey('data_types.id'), nullable=False)
data_type = relationship("DataType", back_populates="dictionary_items", lazy="joined")
# can null if manually add
sns_raw_data_id = Column(Integer, ForeignKey('sns_raw_data.id'), nullable=True)
sns_raw_data = relationship("SnsRawData", back_populates="dictionary_items")
language_id = Column(Integer, ForeignKey('languages.id'), nullable=False, default=1)
language = relationship("Language", back_populates="dictionary_items")
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(DateTime, nullable=False,
default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime, nullable=True)
def __repr__(self):
return 'id: {}'.format(self.id)
#classmethod
def check_is_exists(cls, user_id, text, dictionary_id=None):
if dictionary_id is not None:
count = session.query(DictionaryItem).filter(DictionaryItem.user_id==user_id, DictionaryItem.text==text, DictionaryItem.deleted_at == None, DictionaryItem.id != dictionary_id).count()
else:
count = session.query(DictionaryItem).filter(DictionaryItem.user_id==user_id, DictionaryItem.text==text, DictionaryItem.deleted_at == None).count()
return count > 0
#classmethod
def add_dictionary_item(cls, text=None, phonetic_symbols=None, fgner_entity=None, user_id=None, data_type_id=None, language_id=None, sns_raw_data_id=None):
if not DictionaryItem.check_is_exists(user_id, text):
new_dictionary_item = DictionaryItem(
text=text,
phonetic_symbols=phonetic_symbols,
fgner_entity=fgner_entity,
user_id=user_id,
data_type_id=data_type_id,
language_id=language_id,
sns_raw_data_id=sns_raw_data_id
)
try:
session.add(new_dictionary_item)
session.commit()
session.refresh(new_dictionary_item)
except:
session.rollback()
raise
return new_dictionary_item
else:
return None
Language.dictionary_items = relationship("DictionaryItem", order_by=DictionaryItem.id, back_populates="language")
#event.listens_for(DictionaryItem, 'before_update')
def dictionary_item_before_update(mapper, connection, target):
print("before_update: %d" % target.id)
if target.sns_raw_data_id is not None:
if "language" in target.sns_raw_data.data:
target.language_id = get_language_id_by_language(target.sns_raw_data.data["language"])
if target.language_id is None:
target.language_id = detect_language_id(target.text)
#event.listens_for(DictionaryItem, 'before_insert')
def dictionary_item_before_insert(mapper, connection, target):
print("before_insert: %s" % target.text)
print(target.sns_raw_data, file=sys.stderr)
if target.sns_raw_data_id is not None:
if "language" in target.sns_raw_data.data:
target.language_id = get_language_id_by_language(target.sns_raw_data.data["language"])
if target.language_id is None:
target.language_id = detect_language_id(target.text)
SnsRawData.dictionary_items = relationship("DictionaryItem", order_by=DictionaryItem.id, back_populates="sns_raw_data")
User.dictionary_items = relationship("DictionaryItem", order_by=DictionaryItem.id, back_populates="user")
DataType.dictionary_items = relationship("DictionaryItem", order_by=DictionaryItem.id, back_populates="data_type")
class SnsRawData(Base):
__tablename__ = 'sns_raw_data'
id = Column(Integer, primary_key=True)
data = Column(postgresql.JSON)
status = Column(String, default="waiting")
sns_account_id = Column(Integer, ForeignKey('sns_accounts.id'), nullable=False)
sns_account = relationship("SnsAccount", back_populates="sns_raw_data", lazy="joined")
created_at = Column(DateTime, nullable=False, default=func.now())
updated_at = Column(DateTime, nullable=False,
default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime, nullable=True)
def __repr__(self):
return 'id: {}'.format(self.id)
SnsAccount.sns_raw_data = relationship("SnsRawData", order_by=SnsRawData.id, back_populates="sns_account")
The DictionaryItem have relationship with SnsRawData through sns_raw_data_id.
In DictionaryItem lister_for event('before_insert') i currently getting
target.sns_raw_data.data returning None but target.sns_raw_data_id return a value when i do an insert.
File "/app/models.py", line 230, in dictionary_item_before_insert
if "language" in target.sns_raw_data.data:
AttributeError: 'NoneType' object has no attribute 'data'
How do i access data field from SnsRawData model when i have the Id of it ?
I figured it out, it return None because it was run before insert so there wasn't any relationship to begin with, i just have to query in SnsRawData with sns_raw_data_id from DictionaryItem
#event.listens_for(DictionaryItem, 'before_insert')
def dictionary_item_before_insert(mapper, connection, target):
print("before_insert: %s" % target.text)
if target.sns_raw_data_id is not None:
sns_raw_data = session.query(SnsRawData).filter(SnsRawData.id==target.sns_raw_data_id).first()
if "language" in sns_raw_data.data:
target.language_id = get_language_id_by_language(sns_raw_data.data["language"])
if target.language_id is None:
target.language_id = detect_language_id(target.text)

Order of declaring classes in SqlAlchemy

I'm sory for this newby question, but I can't figure it out for a couple days and it is driving me nuts.
I'm building a small project in order to learn flask, SqlAlchemy and Postrges.
I have major problems with declaring classes in SqlAlchemy. I have already simplified models by removing all many-to-many relationships. However, now I have new problems even with one-to many relationships, although I think I have tried all the possible options. Maybe there's a typo that I keep on overlooking, or I don't grasp something fundamental. Please let me know...
So, I have classes declared as follows in my models.py:
#login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key = True)
username = db.Column(db.String(20), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password = db.Column(db.String(60), nullable=False)
date_registered = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
role = db.Column(db.Integer, nullable=False)
role_exp_date = db.Column(db.DateTime)
#o2o
personal_datas = db.relationship('PersonalData', uselist=False, backref='user', lazy=True)
persons = db.relationship('Person', uselist=False, backref='user', lazy=True)
#o2m
posts = db.relationship('Post', backref='author', lazy=True)
comments = db.relationship('PostComment', backref='author', lazy=True)
projects_owned = db.relationship('ConstrProject', backref='owner', lazy=True)
attachments = db.relationship('Attachment', backref='author', lazy=True)
def __repr__(self):
return f"{self.username} ({self.email})"
class PersonalData(db.Model):
id = db.Column(db.Integer, primary_key = True)
date_birth = db.Column(db.DateTime)
image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
interests = db.Column(db.Text)
experties = db.Column(db.Text) #Потом сделать отдельную таблицу...
#o2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
#o2m
class Person(db.Model):
id = db.Column(db.Integer, primary_key = True)
first_name = db.Column(db.String(30), nullable=False)
middle_name = db.Column(db.String(40), nullable=False)
last_name = db.Column(db.String(60), nullable=False)
email = db.Column(db.String(120))
license = db.Column(db.String(120))
address = db.Column(db.String(240))
telephone = db.Column(db.String(30))
#o2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
#o2m
signers = db.relationship('Signer', backref='person', lazy=True)
def __repr__(self):
return f"{self.last_name.Capitalize} {self.first_name[0].Upper}. {self.middle_name[0].Upper}."
class ConstrProject(db.Model):
__tablename__ = 'constrproject'
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(120), nullable=False, default='New Project')
full_title = db.Column(db.Text, default='New Project')
notes = db.Column(db.Text)
public = db.Column(db.Boolean, default=True) #? check expamples
date_created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
document_template = db.Column(db.Integer, nullable=False, default=1) #later to m2m
print_settings = db.Column(db.Integer, nullable=False, default=1) #later to m2m
address = db.Column(db.String(240))
#o2m
documents = db.relationship('Document', backref='project', lazy=True)
#m2o
owner_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) #+ #default = CurrentUser
developer_id = db.Column(db.Integer, db.ForeignKey('company.id'))
main_contractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
architect_id = db.Column(db.Integer, db.ForeignKey('company.id'))
subcontractor_id = db.Column(db.Integer, db.ForeignKey('company.id'))
other_id = db.Column(db.Integer, db.ForeignKey('company.id'))
developer = db.relationship('Company', foreign_keys=[developer_id], back_populates='constr_projects_developed')
main_contractor = db.relationship('Company', foreign_keys=[main_contractor_id], back_populates='constr_projects_main_contracts')
architect = db.relationship('Company', foreign_keys=[architect_id], back_populates='constr_projects_architect')
subcontractor = db.relationship('Company', foreign_keys=[subcontractor_id], back_populates='constr_projects_subcontracts')
other = db.relationship('Company', foreign_keys=[other_id], back_populates='constr_projects_other')
tech_control_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
main_contractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
architect_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
subcontractor_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
other_reps_id = db.Column(db.Integer, db.ForeignKey('signer.id'), nullable=False)
tech_control_reps = db.relationship('Signer', foreign_keys=[tech_control_reps_id], back_populates='tech_control_projects')
main_contractor_reps = db.relationship('Signer', foreign_keys=[main_contractor_reps_id], back_populates='main_contractor_projects')
architect_reps = db.relationship('Signer', foreign_keys=[architect_reps_id], back_populates='architect_projects')
subcontractor_reps = db.relationship('Signer', foreign_keys=[subcontractor_reps_id], back_populates='subcontractor_projects')
other_reps = db.relationship('Signer', foreign_keys=[other_reps_id], back_populates='others_projects')
def __repr__(self):
return f"Site: {self.name}, (id{self.id})"
class Signer(db.Model):
id = db.Column(db.Integer, primary_key = True)
decree = db.Column(db.String(120))
job_title = db.Column(db.String(120))
date_duty_start = db.Column(db.DateTime)
date_duty_end = db.Column(db.DateTime)
#o2m
person_id = db.Column(db.Integer, db.ForeignKey('person.id'), nullable=False)
company_id = db.Column(db.Integer, db.ForeignKey('company.id'), nullable=False)
#m2o
tech_control_projects = db.relationship('ConstrProject', back_populates='tech_control_reps')
main_contractor_projects = db.relationship('ConstrProject', back_populates='main_contractor_reps')
architect_projects = db.relationship('ConstrProject', back_populates='architect_reps')
subcontractor_projects = db.relationship('ConstrProject', back_populates='subcontractor_reps')
others_projects = db.relationship('ConstrProject', back_populates='other_reps')
def __repr__(self):
return f"{self.job_title} as per {self.decree}." #название компании как подтянуть
class Company(db.Model):
id = db.Column(db.Integer, primary_key = True)
name = db.Column(db.String(60))
full_title = db.Column(db.String(240))
tin = db.Column(db.Integer)
kpp = db.Column(db.Integer)
ogrn = db.Column(db.Integer)
email = db.Column(db.String(120))
address = db.Column(db.String(240))
telephone = db.Column(db.String(30))
#o2m
license_number = db.Column(db.String(40), nullable = False)
license_date_issued = db.Column(db.DateTime)
license_category = db.Column(db.String(120), default = '2nd')
license_issued_by = db.Column(db.String(120))
license_issued_by_tin = db.Column(db.Integer)
license_issued_by_kpp = db.Column(db.Integer)
license_issued_by_ogrn = db.Column(db.Integer)
signers = db.relationship('Signer', backref='company', lazy=True)
constr_projects_developed = db.relationship('ConstrProject', back_populates='developer')
constr_projects_main_contracts = db.relationship('ConstrProject', back_populates='main_contractor')
constr_projects_architect = db.relationship('ConstrProject', back_populates='architect')
constr_projects_subcontracts = db.relationship('ConstrProject', back_populates='subcontractor')
constr_projects_other = db.relationship('ConstrProject', back_populates='other')
def __repr__(self):
return f"{self.name}"
class Post(db.Model):
id = db.Column(db.Integer, primary_key = True)
title = db.Column(db.String(100), nullable=False)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
#o2m
comments = db.relationship('PostComment', backref='Post', lazy=True)
#m2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
def __repr__(self):
return f"Post('{self.title}', '{self.date_posted}')"
class PostComment(db.Model):
id = db.Column(db.Integer, primary_key = True)
date_posted = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
content = db.Column(db.Text, nullable=False)
#m2o
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
post_id = db.Column(db.Integer, db.ForeignKey('post.id'), nullable=False)
def __repr__(self):
return f"Comment('{self.id}', '{self.date_posted}')"
class Document(db.Model):
id = db.Column(db.Integer, primary_key = True)
type = db.Column(db.String(60), nullable=False, default='АОСР')
date_last_edit = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
notes = db.Column(db.Text)
public = db.Column(db.Boolean, default=True)
number = db.Column(db.String(20), nullable=False)
date = db.Column(db.DateTime, default=datetime.utcnow)
job_name = db.Column(db.Text) #? обязательный? на каком этапе делать проверку?
job_place = db.Column(db.String(200))
date_job_start = db.Column(db.DateTime, default=datetime.utcnow)
date_job_end = db.Column(db.DateTime, default=datetime.utcnow)
regulations = db.Column(db.Text)
next_job_allowed = db.Column(db.String(240))
attachments_user_defined = db.Column(db.Text)
#o2m
attachments = db.relationship('Attachment', backref='document', lazy=True)
#m2o
project_id = db.Column(db.Integer, db.ForeignKey('constrproject.id'), nullable=False)
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
#m2m
arch_docs = db.Column(db.Text)
building_materials = db.Column(db.Text)
work_drawings = db.Column(db.Text)
def __repr__(self):
return f"АОСР ('{self.number}', '{self.job_name} {self.job_place}', '{self.project}' )"
class Attachment(db.Model):
id = db.Column(db.Integer, primary_key = True)
type_of_document = db.Column(db.String(60), nullable=False, default="QAC")
number = db.Column(db.String(50), nullable=False)
date = db.Column(db.DateTime)
date_valid_start = db.Column(db.DateTime)
date_valid_end = db.Column(db.DateTime)
contents = db.Column(db.Text)
type_of_file = db.Column(db.String(10), nullable=False, default = 'jpg')
image_file = db.Column(db.String(20), nullable=False)
#m2o
author_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
document_id = db.Column(db.Integer, db.ForeignKey('document.id'), nullable=False)
def __repr__(self):
if self.text:
return f'{self.text}'
return f'+{self.type_of_document} id{self.id} ({self.type_of_file})'
I cannot understand why, when I try to create an instance of a "Document"
I get errors like this:
sqlalchemy.exc.AmbiguousForeignKeysError: Could not determine join
condition between parent/child tables on relationship
Signer.tech_control_projects - 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.
or
sqlalchemy.exc.InvalidRequestError: When initializing mapper
Mapper|ConstrProject|constrproject, expression 'tech_control_reps_id'
failed to locate a name ("name 'tech_control_reps_id' is not
defined"). If this is a class name, consider adding this
relationship() to the class
after both dependent classes have been defined.
I haven't even tried to create these classes. Adding "foreign_keys" doesn't seem to help either. all relationships declared as strings. I tried to use lambda's also without success.
Nevertheless moving the order of class declearations alters the error messages i get...
I cannot find any good examples (resources) of more complicated databases with multiple many-to-many and one-to-many relationships in Each class. Usually examples are very basic and obvious.
So I would really appreciate if you post links to such projects or tutorials.
I wanted to learn some of it myself so I created a working example with one-to-one and multiple one-to-many relationships based on your code (User, Person, Post and Comment classes). I hope it will be a good (simple but not trivial) example for you.
'''SQLAlchemy one-to-one and one-to-many SSCCE'''
import sqlalchemy
import sqlalchemy.ext.declarative
from passlib.hash import pbkdf2_sha256
Base = sqlalchemy.ext.declarative.declarative_base()
class User(Base):
__tablename__ = 'users'
user_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
username = sqlalchemy.Column(sqlalchemy.String(20), unique=True, nullable=False)
email = sqlalchemy.Column(sqlalchemy.String(120), unique=True, nullable=False)
password = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
registered = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
#o2o
person = sqlalchemy.orm.relationship(
'Person',
uselist=False,
back_populates='user',
lazy='joined'
)
#o2m
posts = sqlalchemy.orm.relationship('Post', back_populates='user')
comments = sqlalchemy.orm.relationship('Comment', back_populates='user')
def __repr__(self):
return f'{self.username} ({self.email})'
class Person(Base):
__tablename__ = 'persons'
person_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
primary_key=True
)
first_name = sqlalchemy.Column(sqlalchemy.String(30), nullable=False)
middle_name = sqlalchemy.Column(sqlalchemy.String(40), nullable=False)
last_name = sqlalchemy.Column(sqlalchemy.String(60), nullable=False)
#o2o
user = sqlalchemy.orm.relationship('User', back_populates='person', lazy='joined')
def __repr__(self):
return (
f'{self.last_name.upper()}'
f' {self.first_name[:1].upper()}.'
f' {self.middle_name[:1].upper()}.'
)
class Post(Base):
__tablename__ = 'posts'
post_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
title = sqlalchemy.Column(sqlalchemy.String(100), nullable=False)
posted = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
content = sqlalchemy.Column(sqlalchemy.Text, nullable=False)
#o2m
comments = sqlalchemy.orm.relationship('Comment', back_populates='post')
#m2o
user_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
nullable=False
)
user = sqlalchemy.orm.relationship('User', uselist=False, back_populates='posts', lazy='joined')
def __repr__(self):
return f'Post({self.title!r}, {self.posted!r})'
class Comment(Base):
__tablename__ = 'comments'
comment_id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
posted = sqlalchemy.Column(
sqlalchemy.DateTime(timezone=True),
nullable=False,
server_default=sqlalchemy.func.now()
)
content = sqlalchemy.Column(sqlalchemy.Text, nullable=False)
#m2o
user_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('users.user_id'),
nullable=False
)
user = sqlalchemy.orm.relationship(
'User',
uselist=False,
back_populates='comments',
lazy='joined'
)
post_id = sqlalchemy.Column(
sqlalchemy.Integer,
sqlalchemy.ForeignKey('posts.post_id'),
nullable=False
)
post = sqlalchemy.orm.relationship(
'Post',
uselist=False,
back_populates='comments',
lazy='joined'
)
def __repr__(self):
return f'Comment({self.comment_id!r}, {self.posted!r})'
def main():
engine = sqlalchemy.create_engine(
'postgresql+psycopg2:///stack',
echo=True,
server_side_cursors=True,
use_batch_mode=True
)
Base.metadata.create_all(engine)
Session = sqlalchemy.orm.sessionmaker(bind=engine)
session = Session()
session.commit()
try:
the_user = session.query(User).filter(User.username == 'example').one()
except sqlalchemy.orm.exc.NoResultFound:
the_user = User(
username='example',
email='example#example.com',
password=pbkdf2_sha256.hash('correct horse battery staple')
)
the_user.person = Person(first_name='Ex', middle_name='', last_name='Ample')
session.add(the_user)
print(the_user)
print(the_user.person)
if not the_user.posts:
the_user.posts.append(Post(title='First post', content='Lorem ipsum'))
session.commit()
print(the_user.posts[0])
if not the_user.posts[0].comments:
the_user.posts[0].comments.append(Comment(content='Me too', user=the_user))
session.commit()
print(the_user.posts[0].comments[0])
if __name__ == '__main__':
main()
Several comments:
I don't think it is easy to have foreign-keys both ways for a mandatory one-to-one relationship.
I made both User and Person use the same id numbers, as they are one-to-one.
I made the PostgreSQL now() as the default value for timestamps, instead of client-side utcnow.
I made the timestamps use "timestamp with timezone" type - the "timestamp" type (without timezone) is an abomination.
"user" is a bad name for a table, as this is also a keyword in PostgreSQL, so I changed it to "users". Other tables also changed to plural form for consistency.
A secure password storage is a must.
I used back_populates consistently, as it is more explicit and works better with static code analyzers than backref.

how can I use a column that is not a Primary Key as a Foreign Key?

This might be a dumb question, but I can't find anything that indicates that you can't, however, when I try it, it does not work. I have an example of such an instance, but I can't reproduce the results.
Using SQLAlchemy, here is the working instance:
class Commissions(Base):
__tablename__ = "commissions"
id = Column(
Integer,
Sequence("commission_id", optional=True),
primary_key=True
)
commission_period_name = Column(
Unicode(40), ForeignKey("commission_periods.name"))
commission_period = relationship(
"CommissionPeriods",
primaryjoin="CommissionPeriods.name == Commissions.commission_period_name",
uselist=False,
backref="commissions"
)
agent_id = Column(Integer, ForeignKey("agents.id"))
agent = relationship(
Agents,
primaryjoin=Agents.id == agent_id,
uselist=False,
backref="commissions"
)
create_stamp = Column(DateTime)
commission_type = Column(Unicode(40))
amount = Column(Numeric(10, 2))
transactions_id = Column(Integer, ForeignKey(Transactions.id))
transaction = relationship(Transactions, primaryjoin=Transactions.id ==
transactions_id, backref="commissions", uselist=False)
Note the commission_period uses name, not an id as reference.
Here is the CommissionPeriods table definition:
class CommissionPeriods(Base):
__tablename__ = "commission_periods"
id = Column(
Integer,
Sequence("commission_periods_id", optional=True),
primary_key=True
)
name = Column(Unicode(40), index=True)
start_date = Column(DateTime(), index=True)
end_date = Column(DateTime(), index=True)
network = Column(Unicode(40), index=True)
status = Column(Unicode(40), index=True)
created_by = Column(Unicode(40))
create_stamp = Column(DateTime())
modify_stamp = Column(DateTime())
The alembic scripts runs without errors.
I have tried to replicate the results, with different tables (more or less the same table structures, with a name column that I am trying to use as a FK), but I have had no luck - on the python side everything is okay, but once I try to run the alembic scripts it tells me that the Foreign key is incorrectly formed.
Can someone please explain to me how this works, why it works in the given instance but not when I try to replicate the results from the above?
This is what I have tried in replicating the results:
class Networks(Base):
__tablename__ = "networks"
id = Column(
Integer,
Sequence('networks_id', optional=True),
primary_key=True
)
name = Column(Unicode(40), index=True)
rica_name = Column(Unicode(40))
net_iccid_start = Column(Integer)
net_iccid_end = Column(Integer)
net_iccid_int = Column(Integer)
network_class = Column(Unicode(60))
_config = Column("config", Unicode(2048))
Note that in the above table I want to use the name column as foreign key in:
class AgentRecharges(Base):
__tablename__ = "agent_recharges"
id = Column(
Integer,
Sequence('agent_recharges_id', optional=True),
primary_key=True
)
status = Column(Unicode(40))
create_stamp = Column(DateTime, index=True)
create_by = Column(Integer, ForeignKey(Agents.id))
create_by_agent = relationship(
Agents, primaryjoin=Agents.id == create_by, uselist=False)
modify_stamp = Column(DateTime, index=True)
complete_stamp = Column(DateTime, index=True)
msisdn = Column(Unicode(20), index=True, nullable=False)
amount = Column(Float, index=True, nullable=False)
network_name = Column(Unicode(40), ForeignKey(
"networks.name"), nullable=False)
network = relationship(
"Networks", primaryjoin="Networks.name == AgentRecharges.network_name", uselist=False)
iccid = Column(Unicode(40))
sim = relationship(Sims, backref="agent_recharges")
agents_id = Column(Integer, ForeignKey(Agents.id))
agent = relationship(Agents, primaryjoin=Agents.id ==
agents_id, uselist=False)
transactions_id = Column(Integer, ForeignKey(Transactions.id))
transaction = relationship(Transactions, primaryjoin=Transactions.id ==
transactions_id, backref="agent_recharges", uselist=False)
recharge_batch_id = Column(Integer, ForeignKey(RechargeBatches.id))
recharge_batch = relationship(RechargeBatches)
When I run the alembic script to add this new table, it tells me that the foreign key is incorrectly formed.
Any ideas on why, and how I can accomplish my goal?

errorclass, errorvalue when I try to add new model/table to existing database

When I try to add new model to SQLAlchemy I get error ( I am using MySQL below)
raise errorclass, errorvalue
sqlalchemy.exc.OperationalError 1005 Can't create table
I have model/table PlayerModel/players since beginning of the time and I am trying now to add new table (Base has id column id)
class Base(object):
def __tablename__(self):
return self.__name__.lower()
id = Column(Integer, primary_key=True, nullable=False)
class PlayerModel(Base):
__tablename__ = 'players'
alliances_id = Column(Integer, ForeignKey('alliances.id'), nullable=True)
alliance = relationship('AllianceModel')
username = Column(String(90), nullable=True, default=None)
email = Column(String(75), nullable=True, default=None)
password = Column(String(128), nullable=True, default=None)
money = Column(Integer, nullable=False, default=INITIAL_MONEY)
# used for push notifications
push_token = Column(String(64), nullable=True)
registered_for_push = Column(Boolean, nullable=False, default=False)
device_id = Column(String(64), nullable=True)
device_type = Column(String(64), nullable=True)
device_os = Column(Integer, nullable=True)
notify = Column(Boolean, default=True)
def __repr__(self):
return "<Player('%s')>" % (self.type)
and here throw error
class ArmyModel(Base):
__tablename__ = 'army'
player_id = Column(Integer, ForeignKey('players.id', ondelete='CASCADE'), nullable=False, index=True)
speed = Column(Float, nullable=False, default=1)
# mode of army behavior ( always fight, ignore )
mode = Column(Integer, nullable=False)
x = Column(Float, nullable=False, default=0)
y = Column(Float, nullable=False, default=0)
z = Column(Float, nullable=False, default=0)
def __repr__(self):
return "<Army('%s')>" % (self.type)

Categories

Resources