Common SQLAlchemy operations

Keywords: Database Session Python SQL

This series is part of a note to learn Flask Web Development: A Python-based Web Application Development Practice

For information submitted by users, including accounts, articles, etc., you need to be able to save the data

There are three ways to persist storage:

  • Files: shelves (a combination of pickle and DBM), etc., which provide a dictionary-like object interface
  • Relational Database (SQL)
  • Non-relational Database (NoSQL)
  • Other

Usually a database is used to store information and query it for information

SQL, Relational Database

Relational databases store data in tables, which are implemented in programs using Python classes.For example, there may be tables customers, products, and orders in the order manager's database.

The number of columns in a table is fixed and the number of rows is variable.

Column defines the data attributes of the entity represented by the table.For example, the customers table may have columns such as name, address, phone, and so on.Rows in a table define the actual data for each column.

There is a special column in the table, called the primary key, whose value is the unique identifier of the rows in the table.Tables can also have columns called foreign keys that reference the primary key of a row in the same or different tables.This association between rows is called a relationship, which is the basis of a relational database model.

From this example, you can see that relational databases store data efficiently and avoid duplication.Renaming user roles in this database is also easy because role names only appear in one place.Once the role name is modified in the roles table, all users referencing the role through the role_id will immediately see the update.

On the other hand, storing data in separate tables is still complex.A minor problem with generating a list of users with roles is that before that, users and user roles are read from two tables and joined together.The relational database engine provides the necessary support for join operations.

Store data separately in multiple tables and create joins through foreign keys.Reduce data duplication.Queries are cumbersome, but easy to modify.

Relational databases are:

  • MySQL

  • PostgreSQL

  • SQLite

Specifically, it is a database stored in a single file on a hard disk.Save all data for each database in a single file.Python comes with it.However, only one connection can be accessed at a time.Therefore, it is strongly recommended not to be used in a web application in a production environment.

etc.

NoSQL, non-relational database

  • Key-Value Pairs

Key-value pair data storage is a data structure based on hash mapping.

  • Document-oriented

MongoDB
Riak
Apache CouchDB

Accessing relational databases

Python can access a relational database through a database interface program (DB-API) or object-relational mapping (ORM).

DB-API

Python programs can connect to the target database through API s and read data using SQL statements

connect(), create a connection

close(), close the database connection

commit(), commit

rollback(), rollback/cancel the current

The official specification for Python PEP 0249

MySQL and PostgreSQL are the most common open source databases for storing Python web application data.

  • MySQL

The only MySQL API: MySQLdb

  • PostgreSQL

There are at least three interface programs

  • SQLite

sqlite3

Basic SQL statement

  • Create a database, assign database permissions to one/all users
    CREATT DATABASE test;
    GRANT ALL ON test.* to user;

  • Select the database to use
    USE test;

  • Delete database
    DROP DATABASE test;

  • Create Table
    CREAT TABLE users;

  • Delete Table
    DROP TABLE users;

  • Insert Row
    INSERT INTO users VALUES();

  • Update row
    UPDATE users SET XXX;

  • Delete rows
    DELETE FROM users ;

ORM

To access a database using DB-API, you need to understand the SQL language and be able to write SQL statements. If you do not want to understand SQL and want to use a relational database, you can use ORM

Object Relational Mapping (ORM)

An ORM with a Database at one end and a Python DataObject object at the other end.With ORM, you can operate on databases by manipulating Python objects without writing SQL statements directly.ORM automatically converts Python code into the corresponding SQL statement.The remaining operations, including data checking, generating SQL statements, transaction control, rollback, and so on, are left to the ORM framework to complete.

DataObject s can be generated by Query execution or by user creation.

Of course, ORM can also execute original SQL statements to perform some complex/special operations.

Find all users with a role of "User":

>>> user_role = Role(name='User')
>>> User.query.filter_by(role=user_role).all()  # 
 [<User u'susan'>, <User u'david'>]
To view the native SQL query statement generated by SQLAlchemy for a query, simply `Convert the query object to a string':

>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'

The database abstracts many of the functions of SQL as Python objects, so that you can operate on the database without writing SQL.

Define tables in a database in Flask using Python classes

from flask.ext.sqlalchemy import SQLAlchemy # Import SQLAlchemy from flask extension

db = SQLAlchemy()

class Post(db.Model):
    __tablename__ = 'posts'
    id = db.Column(db.Integer, primary_key=True)
    body = db.Column(db.Text) # Blog body, unlimited length
    timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow) # Time to post
    body_html = db.Column(db.Text) # Store converted HTML Code
    author_id = db.Column(db.Integer, db.ForeignKey('users.id')) # Foreign Key Usage ForeignKey,point User Table id

    comments = db.relationship('Comment', backref='post', lazy='dynamic')

ORM is similar to a standard database interface, but many of the work is handled by ORM instead of directly using the interface.

ORM module for Python: SQLAlchemy, etc.

Some large web development tools/frameworks have their own ORM components.

import os
basedir = os.path.abspath(os.path.dirname(__file__)) # Project Root Directory

SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir, 'app.db') # Path, file name of database file
    # print SQLALCHEMY_DATABASE_URI
    # sqlite:////Users/chao/Desktop/projects/flask/flask_blog/app.db
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository') # Folder, save `SQLAlchemy-migrate` data file, also known as migration policy file
    # print SQLALCHEMY_MIGRATE_REPO
    # /Users/chao/Desktop/projects/flask/flask_blog/db_repository

Configuration of the Database Engine

hello.py

from flask.ext.sqlalchemy import SQLAlchemy # Import SQLAlchemy from flask extension

db = SQLAlchemy()  # Create Database Instance`db`

How to choose

There are several factors to consider.

  • Usability
    If you compare API directly with ORM, it is clear that the latter wins.Object-Relational Mapper (ORM) converts high-level object-oriented operations into low-level database instructions without the user's knowledge.
  • performance
    ORM can be costly in converting object business to database business.In most cases, this reduction in performance is negligible, but not necessarily the same.Ordinarily, ORM improves productivity much more than this slight performance degradation, so the reason for performance degradation is not enough to persuade users to abandon ORM altogether.The real key is how to choose an abstraction layer that directly operates on the lower level of the database in case specific operations require direct optimization using the database's native instructions.
  • Portability
    When choosing a database, you must consider whether it can be used on your development and production platforms.For example, if you plan to leverage cloud platform hosting programs, you need to know what databases this cloud service provides for you to choose from.Portability is also for ORM.Although some ORMs only provide an abstraction layer for one database engine, others may make a higher level of abstraction, supporting different database engines and all using the same object-oriented interface.A good example is SQLAlchemy ORM, which supports many relational database engines, including popular MySQL, Postgres, and SQLite.
  • FLask Integration
    When choosing frameworks, you don't have to choose frameworks that already have Flask integrated, but choosing these frameworks can save you time writing integration code.Using a framework that integrates Flask simplifies configuration and operation, so extensions specifically developed for Flask are your preferred option.

Based on these factors, the database framework chosen for this book is Flask-SQLAlchemy , this Flask extension wrapper SQLAlchemy Frame.

database model

Define Model

In ORM, a model is generally a Python class that represents a table in the database, and the attributes in the class correspond to columns in the database table.

The database instance created by Flask-SQLAlchemy provides the model with a base class, db.Model, and a series of auxiliary classes and functions that can be used to define the structure of the model/table.

The following example defines two tables, one for user roles and one for user information

hello.py

class Role(db.Model):
        __tablename__ = 'roles'
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.String(64), unique=True)

        def __repr__(self):
            return '<Role %r>' % self.name

class User(db.Model):
        __tablename__ = 'users'
        id = db.Column(db.Integer, primary_key=True)
        username = db.Column(db.String(64), unique=True, index=True)

        def __repr__(self):
            return '<User %r>' % self.username

The class variable u tablename_u defines the table name used in the database.If u tablename_u is not defined, Flask-SQLAlchemy uses a default name, but the default table name does not follow the Convention of naming in plural form (plus s), so it is best for us to specify the table name ourselves.

The remaining class variables are columns of the model's properties/tables, defined as instances of the db.Column class.

The first argument to the db.Column class constructor is the type of the database table column/model property.

More types

The remaining parameters in db.Column specify the configuration options for the properties.

Option Name Explain
primary_key If set to True, this column is the primary key of the table
unique If set to True, duplicate values are not allowed in this column
index If set to True, index this column to improve query efficiency
nullable If set to True, this column allows null values; if set to False, this column does not allow null values
default Define default values for this column

Flask-SQLAlchemy requires that each model define a primary key, which is often named id.The id is controlled by Flask-SQLAlchemy.

Other configuration options

Although not mandatory, both models define the u repr() u method, returning a readable string representation model that can be used in debugging and testing.

Database Operation

The best way to learn how to use a model is to actually work in a Python shell.

  • Create Table

First, let Flask-SQLAlchemy create the database from the model class.The method is to use the db.create_all() function:

      (venv) $ python hello.py shell  # Enter the Python shell
      >>> from hello import db   # Import the created database instance from `hello.py`
      >>> db.create_all()

If you look at the program directory, a new file named app.db is created.The name of this SQLite database file is specified in the configuration.If the database table already exists in the database, db.create_all() will not recreate or update the table.This feature is inconvenient if changes are made in the model and you want to apply the changes to an existing database.

The rough way to update an existing database table is to delete the old table and recreate it:

      >>> db.drop_all()
      >>> db.create_all()

Unfortunately, this method has a side effect that we don't want to see, it destroys all the data already in the database.At the end, a method called database migration is described for updating databases.

  • Insert Row
>>> from hello import Role, User
     >>> admin_role = Role(name='Admin')
     >>> mod_role = Role(name='Moderator')
     >>> user_role = Role(name='User')
     >>> user_john = User(username='john', role=admin_role)
     >>> user_susan = User(username='susan', role=user_role)
     >>> user_david = User(username='david', role=user_role)

The constructor of the model accepts the initial value of the model property specified with the keyword parameter.Note that the role attribute is also available, and although it is not a real database column, it is a high-level representation of a one-to-many relationship.The id property of these newly created objects is not explicitly set because the primary key is managed by Flask-SQLAlchemy.These objects now exist only in Python and have not been written to the database.So the id has not been assigned:

     >>> print(admin_role.id)
     None
     >>> print(mod_role.id)
     None
     >>> print(user_role.id)
     None

Changes made to the database through database session management are represented by db.session in Flask-SQLAlchemy.Before you are ready to write objects to the database, add them to the session:

     >>> db.session.add(admin_role)
     >>> db.session.add(mod_role)
     >>> db.session.add(user_role)
     >>> db.session.add(user_john)
     >>> db.session.add(user_susan)
     >>> db.session.add(user_david)

Or abbreviate it as:

     >>> db.session.add_all([admin_role, mod_role, user_role,
     ...     user_john, user_susan, user_david])

To write objects to the database, we call the commit() method to commit the session:

>>> db.session.commit()

Look at the id attributes again, and now they have been assigned:

>>> print(admin_role.id)
1
>>> print(mod_role.id)
2
>>> print(user_role.id)
3

Database sessions ensure database consistency.The commit operation atomically writes all the objects in the session to the database.If an error occurs during the writing session, the entire session will fail.If you always submit changes in your session, you can avoid database inconsistencies caused by partial updates.Consistency: The data in the database is not consistent with the data actually stored.

The database session can also be rolled back.When db.session.rollback() is called, all objects added to a database session that have not yet been committed are restored to their version in the database.

  • Modify line

Calling the add() method on a database session can also update the model.We continue to work in the previous shell session

The following example renames the Admin role to Administrator:

     >>> admin_role.name = 'Administrator'
     >>> db.session.add(admin_role)
     >>> db.session.commit()
  • Delete rows

The database session also has a delete() method.The following example removes the Moderator role from the database:

     >>> db.session.delete(mod_role)
     >>> db.session.commit()

Note that deletions, like inserts and updates, do not occur until the database session is committed.

  • Query Rows

Flask-SQLAlchemy provides query objects for each model class.The most basic model query is to retrieve all the records in the corresponding table:

>>> Role.query.all()
      [<Role u'Administrator'>, <Role u'User'>]
>>> User.query.all()
      [<User u'john'>, <User u'susan'>, <User u'david'>]

Filters allow you to configure query objects for more accurate database queries.The following example finds all users with a role of "User":

>>> User.query.filter_by(role=user_role).all()  # user_role = Role(name='User'), role=user_role
 [<User u'susan'>, <User u'david'>]

To view the native SQL query statement generated by SQLAlchemy for a query, simply convert the query object to a string:

>>> str(User.query.filter_by(role=user_role))
'SELECT users.id AS users_id, users.username AS users_username, users.role_id AS users_role_id FROM users WHERE :param_1 = users.role_id'

If you exit the shell session, the objects created in the previous examples do not exist as Python objects, but as rows in their respective database tables.If you open a new shell session, read the rows from the database and recreate the Python object.

The following example initiates a query to load a user role named "User":

>>> user_role = Role.query.filter_by(name='User').first()

Filters such as filter_by() are called on a query object to return a more precise query object.Multiple filters can be called together until the desired result is obtained.

Common filters that can be called on query objects.

Filter Explain
filter() Add a filter to the original query and return a new query
filter_by() Add an equivalence filter to the original query and return a new query
limit() Use the specified value to limit the number of results returned by the original query and return a new query
offset() Offset the result returned by the original query to return a new query
order_by() Returns a new query by sorting the results of the original query according to the specified criteria
group_by() Returns a new query by grouping the results of the original query according to the specified criteria

After applying the specified filter to the query, execute the query by calling all() to return the results as a list.In addition to all(), there are other ways to trigger query execution.

Common Query Execution Functions

Method Explain
all() Returns all results of the query as a list
first() Returns the first result of the query, or None if there is no result
first_or_404() Returns the first result of the query, terminates the request and returns a 404 error response if there is no result
get() Returns the row corresponding to the specified primary key, or None if no row exists
get_or_404() Returns the row corresponding to the specified primary key, terminates the request and returns a 404 error response if the specified primary key is not found
count() Number of returned query results
paginate() Returns a Paginate object containing results within a specified range

Relationships and queries are handled similarly.

For a complete list see SQLAlchemy query

The following example queries a one-to-many relationship between roles and users from both ends of the relationship:

     >>> users = user_role.users
     >>> users
     [<User u'susan'>, <User u'david'>]
     >>> users[0].role
     <Role u'User'>

The user_role.users query in this example has a minor problem.When the user_role.users expression is executed, the implicit query calls all() to return a list of users.The query object is hidden, so you cannot specify a more precise query filter.For this particular example, it might be better to return an alphabetical list of users.

In examples 5-4, we modified the relationship's settings to include the lazy ='dynamic'parameter, which prevents automatic query execution.

     class Role(db.Model):
         # ...
         users = db.relationship('User', backref='role', lazy='dynamic')
         # ...

After configuring the relationship this way, user_role.users returns a query that has not been executed, so you can add a filter to it:

     >>> user_role.users.order_by(User.username).all()
     [<User u'david'>, <User u'susan'>]
     >>> user_role.users.count()
     2

Operating databases in view functions

Once you've practiced in the Python shell, you can work directly with the database in the view function.

@app.route('/', methods=['GET', 'POST'])
def index():
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username = form.name.data)
            db.session.add(user)   # Not submitted??There is an option in the configuration object, the SQLALCHEMY_COMMIT_ON_TEARDOWN key, to set it to True, `Changes in the database will be submitted automatically after each request ends`
            session['known'] = False
        else:
            session['known'] = True
        session['name'] = form.name.data
        form.name.data = ''
        return redirect(url_for('index'))
return render_template('index.html', form = form, name = session.get('name'), known = session.get('known', False))

After submitting the form, the program uses the filter_by() query filter to find the submitted name in the database.The variable known is written to the user session, so after redirection, you can pass the data to the template to display a custom welcome message.Note that for the program to work properly, you must create database tables in the Python shell as described earlier.

Corresponding new version of template.This template uses the known parameter to add a second line to the welcome message, displaying different content to known and new users.

{% extends "base.html" %}
{% import "bootstrap/wtf.html" as wtf %}
{% block title %}Flasky{% endblock %}
{% block page_content %} 

<div class="page-header">
<h1>Hello, {% if name %}{{ name }}{% else %}Stranger{% endif %}!</h1> 
{% if not known %}
<p>Pleased to meet you!</p>
{% else %}
    <p>Happy to see you again!</p>
{% endif %} 
</div>

{{ wtf.quick_form(form) }}
{% endblock %}

Object Relations Tutorial

Official SQLAlchemy Documentation

Establish a relationship

Translated from Building a Relationship

>>> from sqlalchemy import Column, Integer, String
>>> class User(Base):
...     __tablename__ = 'users'
...
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     fullname = Column(String)
...     password = Column(String)
...
...     def __repr__(self):
...        return "<User(name='%s', fullname='%s', password='%s')>" % ( self.name, self.fullname, self.password)

Let's consider the second table associated with User, which can be mapped and queried.Users can store any number of e-mail address associated user names.This means a one-to-many association from users to a new table Addresses that stores e-mail addresses.We use declarations in Address to define the mapping of this table to User:

>>> from sqlalchemy import ForeignKey
>>> from sqlalchemy.orm import relationship, backref

>>> class Address(Base):
...     __tablename__ = 'addresses'
...     id = Column(Integer, primary_key=True)
...     email_address = Column(String, nullable=False)
...     user_id = Column(Integer, ForeignKey('users.id'))
...
...     user = relationship("User", backref=backref('addresses', order_by=id))
...
...     def __repr__(self):
...         return "<Address(email_address='%s')>" % self.email_address

The above class uses the ForeignKey function, which is a directive applied to Column that indicates that the value of this column should hold the value of a remote column with the specified name.This is a core feature of relational databases, glue, which turns irrelevant tables into collections with rich overlapping relationships.The ForeignKey above indicates that the value of the Addresses.user_id column should be equal to the value in the users.id column, which is the primary key of the users.

The second function, called relationship(), tells ORM that the Address class itself should be linked to the User class using the attribute Address.user.Relationship () uses a foreign key relationship between two tables to determine the nature of this link, in this example, to determine that Address.user will be the many-to-one, many-to-many side.One of the parameters of relationship () is a subfunction of relationship () called backref(), which in turn provides detailed information. That is, add a collection of Address objects corresponding to User to users and save it in User.addresses.The reverse of a many-to-one relationship is always a one-to-many relationship.A complete available relationship() configuration directory is available in Basic Relationship Patterns.

Two complementary relationships, Address.user and User.addresses, are called two-way relationships and are a key feature of SQLAlchemy ORM.Subsection Linking Relationships with Backref The Backref feature is discussed in detail.

In the relationship() parameter, the associated remote class can be specified by a string if the declaring system is in use.In the User class of the example above, once all mappings are completed, these strings are considered Python expressions to generate actual parameters.Allowed names in this evaluation include, among other things, the basis of the declaration that all class names have been created.

Here's an example of using User instead of Address to create the same address/user bidirectional relationship:

class User(Base):
    # ....
    addresses = relationship("Address", order_by="Address.id", backref="user")

Get more details about the parameter style through relationship().

Do you know?

  • Most, though not all, relational databases with foreign key constraints can only link to a primary key column or to a column with a unique constraint.
  • A foreign key constraint that references multiple primary key columns and has multiple columns of its own is called a composite foreign key.It can also reference a subset of these columns.
  • Foreign key columns can be updated automatically to accommodate changes to referenced columns or rows.This is called cascading reference behavior and is a built-in function of a relational database.
  • Foreign keys can reference their own tables.This is a foreign key called a "self-reference".
  • More information about foreign keys Foreign Key - Wikipedia

Use associated objects

Translated from Working with Related Objects

Now, when we create a User object, a blank Addresses collection appears.There are many types of collections, such as sets and dictionaries, which are possible here (more information) Customizing Collection Access ), but by default, a collection is a Python list.

>>> jack = User(name='jack', fullname='Jack Bean', password='gjffdd')
>>> jack.addresses
[]

We are free to add Address objects to User objects.In this example, we assign a complete list directly:

>>> jack.addresses = [
...                 Address(email_address='jack@google.com'),
...                 Address(email_address='j25@yahoo.com')]

When using a two-way relationship, elements are added on one side and automatically appear on the other.This behavior occurs based on property change events, and is judged by Python, without the need for any SQL statements:

>>> jack.addresses[1]
<Address(email_address='j25@yahoo.com')>

>>> jack.addresses[1].user
<User(name='jack', fullname='Jack Bean', password='gjffdd')>

We add Jack Bean to the database session and submit it to the database.Both the jack and two Address members in the corresponding addresses collection are added to the session at once, which uses a process called cascading:

>>> session.add(jack)
>>> session.commit()
INSERT INTO users (name, fullname, password) VALUES (?, ?, ?)
('jack', 'Jack Bean', 'gjffdd')
INSERT INTO addresses (email_address, user_id) VALUES (?, ?)
('jack@google.com', 5)
INSERT INTO addresses (email_address, user_id) VALUES (?, ?)
('j25@yahoo.com', 5)
COMMIT

Query jack, Jack Jack is back.Jack's address is not mentioned in SQL:

>>> jack = session.query(User).filter_by(name='jack').one() 
>>> jack
<User(name='jack', fullname='Jack Bean', password='gjffdd')>
BEGIN (implicit)
SELECT users.id AS users_id,
        users.name AS users_name,
        users.fullname AS users_fullname,
        users.password AS users_password
FROM users
WHERE users.name = ?
('jack',)

Let's look at the addresses collection.Watch SQL:

>>> jack.addresses 
[<Address(email_address='jack@google.com')>, <Address(email_address='j25@yahoo.com')>]
SELECT addresses.id AS addresses_id,
        addresses.email_address AS
        addresses_email_address,
        addresses.user_id AS addresses_user_id
FROM addresses
WHERE ? = addresses.user_id ORDER BY addresses.id
(5,)

When we accessed the addresses collection, SQL suddenly mentioned it.This is an example of delayed loading.The addresses collection is now loaded and behaves like a normal list.We will discuss how to optimize the loading of this collection.

Basic Relationship Patterns

Translated from Official SQLAlchemy Documentation

  • One-to-many

A parent adds a foreign key to the child table to hold the value of the corresponding parent.id, referencing the parent. relationship() Specify in parent, reference/save Associated entries in a batch of child tables.

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'))

In one-to-many mode, establish a two-way relationship where ForeignKey resides in many, specifying the backref option in relationship:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

The table child will therefore get a parent attribute whose value is the entry in the corresponding parent table.

  • Many-to-one

Multiple parents to a child.More than one foreign key is added to the parent table to hold the value of child.id.relationship() is declared in parent, creating a new attribute child, which holds the entries of the associated child table.

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)

The bidirectional behavior is achieved by setting the backref optional parameter with the value "parents" in the relationship.Generate a collection in the Child class to collect the corresponding entries in the parent table.

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref="parents")
  • One-on-one

One-to-one is essentially a two-way relationship in which a number of attributes are set on both sides.To achieve this goal, set a set on the many side of the limited number of properties uselist=False substitution relationship.

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child = relationship("Child", uselist=False, backref="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

Or convert a one-to-many reference to one-to-one and use the backref() function to provide the uselist=False parameter to the reverse end:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref=backref("parent", uselist=False))

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
  • Up to

Many-to-many relationships require adding a relationship Table between two classes.The relationship Table is identified by the relationship() secondary parameter.Usually, tables use MetaData object association announcements from base classes, so ForeignKey's declarations can locate tables at the far end of a link.

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary=association_table)

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

For a two-way relationship, both sides of the relationship contain a set.The backref keyword will automatically use the same secondary parameter for reverse relationships:

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary=association_table, backref="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

The secondary parameter of relationship() also accepts a call that returns the final parameter and is evaluated only when the map is first used.With this, we can define association_table later, as long as all modules can be called after initialization is complete:

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary=lambda: association_table, backref="parents")

By using an extended declaration, the traditional string name of a table is accepted, and matching table names are stored in Base.metadata.tables:

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary="association", backref="parents")

Use Backref Link Relationships

Translated from Linking Relationships with Backref

The backref keyword parameter What does it actually do?

Let's start with a standard user and address context:

from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

The above configuration establishes a collection of associated Addresses objects/entries in User named User.addresses.It also establishes a user property in Address to hold associated User entries.

In fact, the backref keyword is just a common shortcut for placing the second relationship() in Address at the other end of the relationship, setting up an event listener on both sides, and mirroring attribute operations on both sides of the relationship.The above configuration is equivalent to:

from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

    user = relationship("User", back_populates="addresses")

Above, we explicitly added a relationship named User to Address.On both sides of the relationship, the back_populates parameter tells each relationship the associated peer information, indicating that they should establish "two-way" behavior with each other.The primary purpose of this configuration is to add event handlers to relationships, "When an add or set event occurs, set the incoming property to use this property name."This behavior is illustrated below.From a User and an Address Start of instance.The addresses collection is empty, and the user property is None:

>>> u1 = User()
>>> a1 = Address()
>>> u1.addresses
[]
>>> print a1.user
None

However, once Address is added to the u1.addresses collection, all collection and scalar properties are populated:

>>> u1.addresses.append(a1)
>>> u1.addresses
[<__main__.Address object at 0x12a6ed0>]
>>> a1.user
<__main__.User object at 0x12a6590>

This behavior is of course the same in the reverse delete operation, as is the case with both sides.For example, when the user property is set to None again, the Address object is deleted from the reverse collection:

>>> a1.user = None
>>> u1.addresses
[]

Operations on addresses collections and user properties occur entirely in Python without any interaction with the SQL database.If you don't, you need to update the data to the database and reload it after a commit or expiration operation to see the correct state on both sides.The advantage of the backref/back_populates behavior is that common two-way operations reflect the correct state and do not require a database round trip.

Remember that when you use the backref keyword on one side of a relationship, it is the same as when you use back_populates on both sides of a relationship separately.

Backref parameter

We have established the backref keyword as a shortcut to building two separate relationship() structures to reference each other.Part of the behavior of this shortcut is to determine that the configuration parameters applied to relationship() will also be applied in another direction -- that is, those parameters describe the relationship at the schema level and are unlikely to be different in the opposite direction.Typically, a many-to-many relationship has a secondary parameter, or one-to-many or one-to-many has a primaryjoin parameter.For example, if we restrict the Adress object in the list to start with tony:

from sqlalchemy import Integer, ForeignKey, String, Column
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address",
                    primaryjoin="and_(User.id==Address.user_id, "
                        "Address.email.startswith('tony'))",
                    backref="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

We can observe that by examining the resulting content, the jion condition applies to both sides of the relationship:

>>> print User.addresses.property.primaryjoin
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
>>> print Address.user.property.primaryjoin
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>

Reused parameters should all do the "right thing" - they only use the appropriate parameters, and in many-to-many relationships, primaryjoin and secondaryjoin will be used in reverse for the other end.

Most often, we want to specify the parameters used by the other end of the backref.This includes parameters for relationship(), such as lazy,remote_side, cascade, cascade_backrefs.In this case, we use the backref() function instead of the string:

# <other imports>
from sqlalchemy.orm import backref

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address",
                    backref=backref("user", lazy="joined"))

Above, we only put a lazy="joined" parameter in Address.user, indicating that when a query against Address occurs, the join of a User instance should automatically populate the user attribute of each returned Address.The backref() function is formatted as a parameter that we interpret as an additional parameter applied to the new relationship it creates.

Database Migration

During the process of developing your program, you will find that sometimes you need to modify the database model, such as adding tables, adding columns, and updating the database after the modification, so you need to migrate the database

A better way to update tables is to use the database migration framework.Source version control tools track changes in source files. Similarly, the database migration framework tracks changes in database schemas, and incrementally applies changes to the database.

The main developer for SQLAlchemy wrote a migration framework called Alembic .In addition to using Alembic directly, Flask programs can also be used Flask-Migrate Extension.This extension wraps Alembic lightweight and integrates it into Flask-Script, all with Flask-Script commands.

  • Create a migration warehouse

install Flask-Migrate:

(venv) $ pip install flask-migrate

Initialize and configure Flask-Migrate

from flask.ext.migrate import Migrate, MigrateCommand
# ...
migrate = Migrate(app, db)  # Initialization
manager.add_command('db', MigrateCommand) # On the command line, call `MigrateCommand'with `db`
➜  flask_blog git:(master) ✗ python run.py
usage: run.py [-?] {shell,db,runserver} ...

positional arguments:
  {shell,db,runserver}
    shell               Runs a Python shell inside Flask application context.
    db                  Perform database migrations
    runserver           Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit

To export database migration commands, Flask-Migrate provides a MigrateCommand class that can be attached to the manager object of Flask-Script.In this example, the MigrateCommand class is attached using the db command.

Before maintaining the database migration, use the init subcommand to create the migration repository:

(venv) $ python hello.py db init    # A `migrations'folder will be added to the application.Files in folders need to be versioned with other source files.➜  

flask_blog git:(master) ✗ python run.py db init
  Creating directory /Users/chao/Desktop/projects/flask/flask_blog/migrations ... done
  Creating directory /Users/chao/Desktop/projects/flask/flask_blog/migrations/versions ... done
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/alembic.ini ... done
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/env.py ... done
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/env.pyc ... done
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/README ... done
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/script.py.mako ... done
  Please edit configuration/connection/logging settings in '/Users/chao/Desktop/projects/flask/flask_blog/migrations/alembic.ini'
  before proceeding.

This command creates the migrations folder in which all migration scripts are stored.

Files in the database migration repository need to be versioned along with other files in the program.

  • Create migration scripts

In Alembic, database migration is represented by a migration script.There are two functions in the script, upgrade() and downgrade().The upgrade() function applies the changes in the migration to the database, and the downgrade() function deletes the changes.Alembic has the ability to add and delete changes, so the database can be reset to any point in the modification history.

Alembic migrations can be created manually using the revision command or automatically using the migrate command.

The manually created migration is just a skeleton, the upgrade() and downgrade() functions are empty, and the developer uses the Operations object instructions provided by Alembic to implement the specific operation.

Automatically created migrations generate the contents of upgrade() and downgrade() functions based on the differences between the model definition and the current state of the database.

Auto-created migrations are not always correct and may miss some details.Be sure to check the migration scripts after they are automatically generated.

The migrate subcommand is used to automatically create migration scripts:

     (venv) $ python hello.py db migrate -m "initial migration" # Generate an initial migration
     INFO  [alembic.migration] Context impl SQLiteImpl.
     INFO  [alembic.migration] Will assume non-transactional DDL.
     INFO  [alembic.autogenerate] Detected added table 'roles'
     INFO  [alembic.autogenerate] Detected added table 'users'
     INFO  [alembic.autogenerate.compare] Detected added index 'ix_users_username' on '['username']'
Generating /home/flask/flasky/migrations/versions/1bc 594146bb5_initial_migration.py...done
➜  flask_blog git:(master) ✗ python run.py db migrate -m 'migration'
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
INFO  [alembic.autogenerate.compare] Detected added table 'post'
  Generating /Users/chao/Desktop/projects/flask/flask_blog/migrations/versions/0fb164ef6c11_migration.py ... done
  • Update database

After reviewing and correcting the migration script, we can use the db upgrade command to apply the migration to the database:

     (venv) $ python hello.py db upgrade
     INFO  [alembic.migration] Context impl SQLiteImpl.
     INFO  [alembic.migration] Will assume non-transactional DDL.
     INFO  [alembic.migration] Running upgrade None -> 1bc594146bb5, initial migration
➜  flask_blog git:(master) ✗ python run.py db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 0fb164ef6c11, migration

For the first migration, it works the same as calling the db.create_all() method.However, in subsequent migrations, the upgrade command can apply changes to the database without affecting the data stored in it.

Posted by phpmaverick on Fri, 05 Jul 2019 09:23:21 -0700