SQL Alchemy ORM for Postgresql
=======================================
Flask-Philo uses SQLAlchemy as its Object Relational Mapper (ORM). For mode detail and documentation on SQL Alchemy, visit ``_
Where is Flask-Philo's PostgreSQL ORM implementation?
-----------------------------------------------
Flask-Philo's PostgreSQL ORM can be found at:
``_
Database Settings
-----------------
The first
thing you need to do is to add the relevant configuration
to your application's settings file, typically ``/config/development.py`` :
::
DATABASES = {
'POSTGRESQL': {
'DEFAULT': 'postgresql://user:password@host:port/database_name',
},
}
Models
======
In order to create Flask-Philo Models, simply create classes that inherit from Flask-Philo's ``BaseModel`` class:
``flask_philo.db.postgresql.orm.BaseModel``
``BaseModel`` exposes a number of useful methods for retrieving and manipulating data:
* **add()** - create a new Flask-Philo class instance (ORM object)
* **update()** - modify an existing ORM object
* **delete()** - delete an ORM object
* **objects.get(key=value)** - retrieve an ORM object by a specified key
* **objects.filter_by(key=value)** - retrieve a collection of filtered objects by a specified key/keys
* **objects.count()** - count all object instances of a Flask-Philo class
* **objects.raw_sql(sql_query_string)** - run direct SQL queries on your application's database
...examples for each of these methods are included in the **Data Manipulation Examples** section below
Example Models
-------------
Here are 3 examples of models with some simple properties and examples of how you can use them in
your application:
::
from flask_philo.db.postgresql.orm import BaseModel
from sqlalchemy import Column, ForeignKey, Integer, String
from sqlalchemy.orm import relationship
class Artist(BaseModel):
__tablename__ = 'artist'
name = Column(String(256), nullable=False)
description = Column(String(256))
albums = relationship('Album', backref='artist')
is_famous = Column(Boolean, default=False)
genre_id = Column(Integer, ForeignKey('genre.id'))
class Album(BaseModel):
__tablename__ = 'album'
name = Column(String(256), nullable=False)
description = Column(String(256))
artist_id = Column(Integer, ForeignKey('artist.id'))
class Genre(BaseModel):
__tablename__ = 'genre'
name = Column(String(256), nullable=False)
description = Column(String(256))
Flask-Philo's ORM automatically handles the creation of each model's integer ``id`` property, along with automatically creating and updating the timestamp fields ``created_at`` and ``updated_at``
Foreign Keys may be defined using the ``ForeignKey()`` syntax, as is the case in our example **Album** model above. Here, the **Album** model references the **Artist** model (DB table ``artist``) as a foreign key using ``artist.id``
Fields
===========
Field types
-----------
The most common field types are:
- **Column**: defines the properties of a given column
- **relationship**: defines the relationship between two tables
Examples:
::
class Artist(BaseModel):
__tablename__ = 'artist'
name = Column(String(256))
description = Column(String(256))
albums = relationship('Album', backref='artist')
genre_id = Column(Integer, ForeignKey('genre.id'))
Supported data types
-----------
- **String**: stores string format data
::
name = Column(String(256))
- **Integer**: stores integer format data
::
amount = Column(Integer)
- **Boolean**: stores boolean format data
::
is_famous = Column(Boolean, default=False)
- **Numeric**: store numbers with a very large number of digits. Scale is the count of decimal digits in the fractional part. Precision refers to the total count of digits in the whole number.
::
tempo = Column(Numeric(precision=32, scale=16))
- **ARRAY**: store array data
::
possible_names = Column(ARRAY(String(256)))
- **JSON**: stores JSON format data
::
config_dict = Column(JSON)
- **Enum**: provides a set of possible string values that work as constraints for the given column.
::
day = Column(
Enum(
'sunday', 'monday', 'tuesday', 'wednesday', 'thursdat', 'fruday',
'saturday', name="days_of_the_week"))
Field options
-----------
The following ORM constraints can be set in your Flask-Philo Model:
- **PrimaryKey**: specifies that a given column is a primary key. As such, it is unique and not nullable.
::
id = Column(Integer, primary_key=True)
- **ForeignKey**: specifies a column that acts as foreign key, thereby defining a relationship with another table
::
genre_id = Column(Integer, ForeignKey('genre.id'))
- **unique**: specifies that a column must have a unique value for each record
::
name = Column(String(256), unique=True)
- **nullable**: specifies if a column accepts null values or not
::
name = Column(String(256), nullable=False, unique=True)
- **default**: defines a default value in the case that it is not specified
::
is_famous = Column(Boolean, default=False)
----
Database DML Operations
=======================
Postgresql Connection Pool
------------------------------
As a design decision, management of the PostgreSQL connection is the responsability of the developer, but this is made simple with Flask-Philo's built-in connection management methods.
* to instantiate a DB session, we use Flask-Philo's ``get_pool()`` method
* after modifying, creating or removing data in a session, we must commit or rollback the session using Flask-Philo's ``pool.commit()`` or ``pool.rollback()`` methods
Opening a Flask-Philo DB session
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
::
from flask_philo.db.postgresql.connection import get_pool
pool = get_pool()
# Do some ORM operations here
pool.commit()
The following examples demonstrate each of the core ORM operations you will commonly use to query the PostgreSQL database
Data Manipulation Examples
------------------
Adding a new record
^^^^^^^^^^^^^^^
In this example, we create a new **Genre** using the same model defined in the **Example Models** section:
::
pool = get_pool()
rock = Genre(name='Rock', description='Rock and Roll')
rock.add()
At this point, we have added a new instance of the **Genre** model to our DB session, but we still need to either ``commit()`` or ``rollback()`` the insert operation
To commit the operation and create a new record:
::
pool.commit()
...alternatively, if the record is not needed the transaction can be rolled-back, and nothing will be changed in the PostgreSQL database:
::
pool.rollback()
Retrieving a specific record
^^^^^^^^^^^^^^^^^^
Now that we've created and committed our new 'Rock' genre, we can retrieve the record directly from the database by using the ``objects.get()`` function:
::
genre_obj = Genre.objects.get(name="Rock")
genre_name = genre_obj.name
genre_id = genre_obj.id
print("Genre", genre_id, ":", genre_name) # Will print "Genre 13 : Rock"
...we can also retrieve a record that matches *multiple* field values:
::
genre_obj = Genre.objects.get(id=13, name="Rock")
print("Genre", genre_obj.id, ":", genre_obj.name) # Will print "Genre 13 : Rock"
Filtering records
^^^^^^^^^^^^^^^^^^^^
We may also use Flask-Philo's ``filter_by()`` function to filter records and retrieve a collection of all matching instances of the desired model.
Continuing our **Genre** example from earlier sub-sections:
::
genre_collection = Genre.objects.filter_by(name="Rock")
genre_obj = genre_collection.first()
print("Genre", genre_obj.id, ":", genre_obj.name) # Will print "Genre 13 : Rock"
Updating a record
^^^^^^^^^^^^^^^
Just as we can retrieve a record, we can update records in a similar manner:
::
genre_obj = Genre.objects.filter_by(name="Rock").first()
genre_obj.name = "Metal"
genre_obj.update()
pool.commit()
updated_genre_obj = Genre.objects.filter_by(name="Metal").first()
print("Genre", updated_genre_obj.id, ":", updated_genre_obj.name) # Will print "Genre 13 : Metal"
Deleting a record
^^^^^^^^^^^^^^^
In the same way we've added and updated a record, we can also delete it:
::
genre_obj = Genre.objects.filter_by(name="Metal").first()
genre_obj.delete()
pool.commit()
genre_obj = Genre.objects.filter_by(name="Metal").first() # genre_obj == None
..once we have committed the ``delete()`` operation, this record no longer exists in our PostgreSQL DB.
Counting records
^^^^^^^^^^^^^^^^
To count the number of instances of a given Model, we can use the ``objects.count()`` method.
::
genre_count = Genre.objects.count()
print(genre_count, "Genres present") # Will print "13 Genres present"
Querying using Raw SQL
^^^^^^^^^^^^^^^
While the use of SQLAlchemy ORM will automatically translate Flask-Philo method
calls to their corresponding PostgreSQL queries, we also provide a means of
directly querying our underlying PostgreSQL database with a raw SQL query.
By passing a valid SQL query-string to the ``objects.raw_sql()`` method, we can
retrieve or update data explicitly, as is the case in the following examples:
Retrieving data by raw SQL:
::
raw_sql_genre_result = Genre.objects.raw_sql("SELECT description FROM genre WHERE name='Rock';").fetchone()
genre_description = raw_sql_genre_result.description
genre_name = raw_sql_genre_result.name
print(genre_name, "genre description :", genre_description) # Will print "Rock genres description : Rock and Roll"
Modifying data by raw SQL:
::
query_string = "UPDATE genre SET name='Indie' WHERE id = 13"
Genre.objects.raw_sql(query_string)
Data manipulation with Relationships
^^^^^^^^^^^^^^^^^^^^^^^
The following example demonstrates the creation and retrieval of objects for two
related models, **Album** and **Artist**, as defined in the *Example Models* section above
::
# Create and commit an artist record
floyd_artist_obj = Artist(name='Pink Floyd')
floyd_artist_obj.commit()
pink_floyd_id = floyd_artist_obj.id
pool.commit()
# Create and commit a related album
dark_album_obj = Album(
artist_id=pink_floyd_id, name='Dark side of the moon')
dark_album_obj.add()
pool.commit()
# Create and commit another related album by the same artist
wall = Album(
artist_id=pink_floyd_id, name='The Wall',
description='Interesting')
wall.add()
pool.commit()
# Retrieve all albums by Pink Floyd
album_results = Album.objects.filter_by(artist_id=pink_floyd_id)
for album_obj in album_results:
print("Pink Floyd album :", album_obj.name)
# Will print:
# Pink Floyd album : Dark side of the moon
# Pink Floyd album : The Wall
Using multiple Postgresql databases
-------------------------------------
Flask-Philo allows you to connect to multiple PostgreSQL database instances from the same
application.
To take advantage of this feature, simply add a ``DATABASES`` block in an application
configuration file in ``src/config``.
Here's an example of a typical configuration file:
::
DATABASES = {
'POSTGRESQL': {
'DEFAULT': 'postgresql://user:password@host:port/database_name',
'MUSIC_CATALOG': 'postgresql://user:password@host:port/songs_database_name',
}
}
...with this configuration in place, we can now access a specific database while using Flask-Philo's ``get_pool()`` method:
::
from flask_philo.db.postgresql.connection import get_pool
pool = get_pool()
# Add a Genre object to our session
blues_obj = Genre(name='Blues', description='Still got the blues')
blues_obj.add(connection_name='MUSIC_CATALOG')
# Commit changes to the MUSIC_CATALOG database
pool.commit(connection_name='MUSIC_CATALOG'))
The ``connection_name==DB_NAME`` parameter may be specified for all other common ORM methods in Flask-Philo