Insert a nested schema into a database with fastAPI? - python-3.x

I have recently come to know about fastAPI and worked my way through the tutorial and other docs. Although fastAPI is pretty well documented, I couldn't find information about how to process a nested input when working with a database.
For testing, I wrote a very small family API with two models:
class Member(Base):
__tablename__ = 'members'
id = Column(Integer, primary_key=True, server_default=text("nextval('members_id_seq'::regclass)"))
name = Column(String(128), nullable=False)
age = Column(Integer, nullable=True)
family_id = Column(Integer, ForeignKey('families.id', deferrable=True, initially='DEFERRED'), nullable=False, index=True)
family = relationship("Family", back_populates="members")
class Family(Base):
__tablename__ = 'families'
id = Column(Integer, primary_key=True, server_default=text("nextval('families_id_seq'::regclass)"))
family_name = Column(String(128), nullable=False)
members = relationship("Member", back_populates="family")
and I created a Postgres database with two tables and the relations described here. With schema definitions and a crud file as in the fastAPI tutorial, I can create individual families and members and view them in a nested fashion with a get request. Here is the nested schema:
class Family(FamilyBase):
id: int
members: List[Member]
class Config:
orm_mode = True
So far, so good. Now, I would like to add a post view which accepts the nested structure as input and populates the database accordingly. The documentation at https://fastapi.tiangolo.com/tutorial/body-nested-models/ shows how to do this in principle, but it misses the database (i.e. crud) part.
As the input will not have id fields and obviously doesn't need to specify family_id, I have a MemberStub schema and the NestedFamilyCreate schema as follows:
class MemberStub(BaseModel):
name: str
age: int
class NestedFamilyCreate(BaseModel):
family_name: str
members: List[MemberStub]
In my routing routine families.py I have:
#app.post('/nested-families/', response_model=schemas.Family)
def create_family(family: schemas.NestedFamilyCreate, db: Session = Depends(get_db)):
# no check for previous existence as names can be duplicates
return crud.create_nested_family(db=db, family=family)
(the response_model points to the nested view of a family with all members including all ids; see above).
What I cannot figure out is how to write the crud.create_nested_family routine. Based on the simple create as in the tutorial, this looks like:
def create_nested_family(db: Session, family: schemas.NestedFamilyCreate):
# split information in family and members
members = family.members
core_family = None # ??? This is where I get stuck
db_family = models.Family(**family.dict()) # This fails
db.add(db_family)
db.commit()
db.refresh(db_family)
return db_family
So, I can extract the members and can loop through them, but I would first need to create a new db_family record which must not contain the members. Then, with db.refresh, I would get the new family_id back, which I could add to each record of members. But how can I do this? If I understand what is required here, I would need to achieve some mapping of my nested schema onto a plain schema for FamilyCreate (which works by itself) and a plain schema for MemberCreate (which also works by itself). But how can I do this?

I found a solution after re-reading about Pydantic models and their mapping to dict.
in crud.py:
def create_nested_family(db: Session, family: schemas.NestedFamilyCreate):
# split information in family and members
family_data = family.dict()
member_data = family_data.pop('members', None) # ToDo: handle error if no members
db_family = models.Family(**family_data)
db.add(db_family)
db.commit()
db.refresh(db_family)
# get family_id
family_id = db_family.id
# add members
for m in member_data:
m['family_id'] = family_id
db_member = models.Member(**m)
db.add(db_member)
db.commit()
db.refresh(db_member)
return db_family
Hope, this may be useful to someone else.

Related

How to handle foreign-keys during the iteration through attributes of Django model (Python)?

Dear Django/Python experts. I have a Django model (python class) which contain standard fields and also fields represented by foreign keys. It is easy to iterate throught attributes of a model however I have no idea how to handle foreign keys?
Here is a model nr.1 Employee containing foreign key which refers to another model EmployeeLocation:
class Employee(models.Model):
firstname = models.CharField(max_length=128)
lastname = models.CharField(max_length=128)
location = models.ForeignKey(EmployeeLocation, on_delete=models.CASCADE)
and here is a model nr.2 EmployeeLocation:
class EmployeeLocation(models.Model):
id = models.BinaryField(primary_key=True, max_length=16, null=False)
city = models.CharField(max_length=32)
and now I iterate via attributes of Employee in the following way:
# Collecting names of class fields.
field_names = [f.name for f in Employee._meta.get_fields()]
for current_attribute in field_names:
field_value = str(getattr(my_employee, current_attribute))
This solution works fine for standard attributes but does not return values when reaches the location which is a foreign key.
To tackle this issue I did the following stunts :) :
I have made a dictionary containing names of foreign keys and as values I have placed Django queryset, that gets a value - but this is not an elegant hack :) In this way then iteration ecounters attribute which is foreign-key, it takes value from dictionary (which is generated by queryset):
FKs = {'location': EmployeeLocation.objects.filter(id=my_employee.location_id)[0].city,
...
...}
# Collecting names of class fields.
field_names = [f.name for f in Employee._meta.get_fields()]
for current_attribute in field_names:
if current_attribute in FKs.keys():
field_value = FKs[current_attribute]
else:
field_value = str(getattr(my_employee, current_attribute))
Please tell me in simple way how shall I realize it properly. Thank you so much in advance :)

Best practice for relate SQLAlchemy model with additional runtime data?

I'm going to use SQLAlchemy ORM for my model of user in Python.
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user_id'))
Additionally I want to bind to each User data that shouldn't be saved in database, because it has meaning only during runtime. Such as link to some objects (asyncio.lock(), asyncio.Task()).
Ofcourse when I request data from database new object will be returned. So my question is what is best practice to relate such User(Base) objects with additional data that is existing while my app is working.
So I want something like this, but I wonder about proper pattern for that or more elegant solution. Here I need to save some key such as user_id, to relate some User extracted from DB to the instance of UserRuntimeData.
class UserRuntimeData():
def __init__(self, ...):
self.user_id = ...
self._lock = ...
self._current_task = ...
def compare(self, user: User) -> bool:
if user.user_id == self.user_id:
return True
return False
UPD. I have thought that I could also save runtime data by serializing in byte code and then just extract whole User info from DB, but I think it's kind of mess.

After I added the association table nothing is committed into the database

I'm building an app for rating beers at an event. The beers one can rate should be added to a table, as well should the event be added to another table and the beers and the event should be connected. Since at an event there is more than just one beer to be tasted and a beer can be tasted at multiple events, I want to make a m:n-relationship. I'm doing this with python3, I'm using flask and flasksqlalchemy. I'm using an sqlite-database.
The model I builded sofar looks like this:
#association table
event_beer = db.Table('event_beer',
db.Column('event_id', db.Integer, db.ForeignKey('event.id'), primary_key=True),
db.Column('beer_id', db.Integer, db.ForeignKey('beer.id'), primary_key=True))
class Event(db.Model):
__tablename__ = 'event'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
def __str__(self):
return f'{name}'
class Beer(db.Model):
__tablename__ = 'beer'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), nullable=False)
event = db.relationship('Event', secondary=event_beer)
def __str__(self):
return f'{self.name}, {self.event}'
I omitted a few Columns which don't have any Foreign Keys or so for the sake of simplicity. The code which is executed when I want to save the recorded data is:
event = Event(name = 'event_name')
beer1 = Beer(name = 'beerone')
beer2 = Beer(name = 'beertwo')
beer1.event.append(event)
beer2.event.append(event)
db.session.commit()
The values to be saved aren't strings, but for the sake of simplicity I replaced them. The values are there though and in the database there aren't any empty rows.
I don't know whether I set up the model wrong or whether it's an issue while committing. Any help would be appreciated.
I'm deeply sorry, I just found the problem. Obviously I forgot to add the items to the session. All that missed were db.session.add(event). I was trying to figure this out for at least 6 hours now but I just found it after I posted the problem to stackoverflow.

How do I dynamically create forms with Python Flask

I have a large model which I have defined in my models.py file and I have the form which serves it. Do I have to explicitly state each field for the form or is there a way to dynamically create this so I don't have repetition in my code?
forms.py
class CustomerForm(FlaskForm): # must be a better way
""" Declarative Form base class responsible for customers
variable names match customers/models.py and in the corresponding HTML files """
tpi_name = StringField('Enter Your Name')
tpi_ref = StringField('Enter Your Email')
company_name = StringField('Company Name (*)', validators=[InputRequired()])
company_type = StringField('Company Type')
company_reg = StringField('Company Registration Number')
and forms.py
class Post(db.Model): # Must be a better way
""" Creates the table Post and models the structure of the database table """
tpi_name = db.Column(db.String(100), nullable=False)
tpi_ref = db.Column(db.String(100), nullable=False)
company_name = db.Column(db.String(100), nullable=False)
company_type = db.Column(db.String(100), nullable=False)
company_reg = db.Column(db.Integer, nullable=False)
So there is a lot of repetition here, is there a better way?
I've not personally tried this, but came across this: http://flask.pocoo.org/snippets/60/
Sounds like that's discussing what you're trying to achieve if I understand your question correctly.
Maybe you like using flask-admin library: https://flask-admin.readthedocs.io/en/latest/
How does it work? The basic concept behind Flask-Admin, is that it lets you build
complicated interfaces by grouping individual views together in classes: Each web page
you see on the frontend, represents a method on a class that has explicitly been added
to the interface.
These view classes are especially helpful when they are tied to particular database
models, because they let you group together all of the usual Create, Read, Update,
Delete (CRUD) view logic into a single, self-contained class for each of your models.
Example:
from flask_admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
admin = Admin(app, name='microblog', template_mode='bootstrap3')
admin.add_view(ModelView(User, db.session))
admin.add_view(ModelView(Post, db.session))

Dynamic SQLAlchemy ORM relationship generation

Premise: I have a lot of tables that have to individually created (they cannot be dynamically created) and therefore, I find myself constantly having to make mixins that allow the standardization of relating tables:
class A_Table(Base):
id = Column(Integer, primary_key=True)
class A_Relator(My_Mixin_Base):
#declared_attr
def a_table_id(cls):
return Column(ForeignKey(A_Table.id))
#declared_attr
def a_table(cls):
return relationship(A_Table)
class B_Table(A_Relator, Base):
id = Column(Integer, primary_key=True)
class C_Table(A_Relator, Base):
id = Column(Integer, primary_key=True)
class D_Table(A_Relator, Base):
id = Column(Integer, primary_key=True)
# ad nauseam
Simple, but when B_Table, C_Table, etc. all have their own Relator classes, it gets very repetitive, and thus, something that should be easily solved in code.
My Solution: I made a class factory (?) that creates a mixin class to be used one time.
def related(clss, defined=False, altName=None):
class X((Definer if defined else Relator),):
linkedClass = clss
#classmethod
def linkedClassFieldName(cls):
return "{}Id".format(clss.getBackrefName())
def linkId(cls):
return Column(ForeignKey(clss.id))
def linkRe(cls):
return relationship(clss,
foreign_keys=getattr(cls, "{}Id".format(clss.getBackrefName() if not altName else altName)),
backref=cls.getBackrefName())
setattr(X, "{}Id".format(clss.getBackrefName() if not altName else altName), declared_attr(X.linkId))
setattr(X, "{}".format(clss.getBackrefName() if not altName else altName), declared_attr(X.linkRe))
del X.linkId
del X.linkRe
return X
Which allows you to do the following and be done with it:
class B_Table(related(A_Table), Base):
id = Column(Integer, primary_key=True)
...but this is messy and confusing, and I would guess there is a much better way to do this that leaves a lot less to uncertainty.
Question: I'm looking for a way to do this in a more direct SQLAlchemy-aligned way with less roundabout "hack". Or in summary: how do I make a generic SQLAlchemy mixin that generates a relationship?
I had a mess around with this. Not sure how well this solution will suit your needs but I did it as more of a learning exercise for myself, and if it helps for you, then great.
So with the objective to be able to have foreign keys and relationships defined on models with as little input as possible, this is what I came up with.
Here are the models that I used:
class Base:
#declared_attr
def __tablename__(cls):
return cls.__name__.lower()
#declared_attr
def id(cls):
return Column(Integer, primary_key=True)
def __repr__(self):
return f'<{type(self).__name__}(id={self.id})>'
Base = declarative_base(cls=Base)
class A_Table(Base):
parents = []
class B_Table(Base):
parents = ['A_Table']
class C_Table(Base):
parents = ['A_Table', 'B_Table']
Notice the class variable parents on each model which is a sequence of strings that should be other model names that inherit from the same declarative_base instance. Foreign keys and relationships to the parent classes will be created on the class that declares them as parents.
So then leveraging off of the fact that:
Attributes may be added to the class after its construction, and they
will be added to the underlying Table and mapper() definitions as
appropriate
(see docs)
I iterate through all of the models that are defined on Base and build the required objects according to the parents it's given and plug them in.
Here's the function that does all of that:
from sqlalchemy import inspect # this would be the only new import you'd need
def relationship_builder(Base):
""" Finds all models defined on Base, and constructs foreign key
columns and relationships on each as per their defined parent classes.
"""
def make_fk_col(parent):
""" Constructs a Column of the same type as the primary
key of the parent and establishes it as a foreign key.
Constructs a name for the foreign key column and attribute.
"""
parent_pk = inspect(parent).primary_key[0]
fk_name = f'{parent.__name__}_{parent_pk.name}'
col = Column(
fk_name, parent_pk.type,
ForeignKey(f'{parent.__tablename__}.{parent_pk.name}')
)
return fk_name, col
# this bit gets all the models that are defined on Base and maps them to
# their class name.
models = {
cls.__name__: cls for cls in Base._decl_class_registry.values() if
hasattr(cls, '__tablename__')
}
for model in models.values():
for parentname in model.parents:
parent = models.get(parentname)
if parent is not None:
setattr(model, *make_fk_col(parent))
rel = relationship(parent, backref=model.__name__)
setattr(model, parentname, rel)
To test, this is just at the bottom of the same module that I've got everything else defined in:
if __name__ == '__main__':
relationship_builder(Base)
a = A_Table(id=1)
b = B_Table(id=1)
c = C_Table(id=1)
a.B_Table.append(b)
a.C_Table.append(c)
b.C_Table.append(c)
print(b.A_Table)
print(c.A_Table)
print(c.B_Table)
# <A_Table(id=1)>
# <A_Table(id=1)>
# <B_Table(id=1)>
Here's the schema it created:
This won't work for composite primary/foreign keys but I don't think it would be too much of a stretch to get it there. If len(inspect(parent).primary_keys) > 1 you'd need to build ForeignKeyConstraints and add them to the table definition, but I haven't tested that at all.
I also don't think it would be too much of a stretch to make it fully automated if you could name your models in such a manner that the subordination of a model could be inferred from the name of the model itself. Again, just thinking out loud.

Resources