• Stars
    star
    120
  • Rank 294,229 (Top 6 %)
  • Language
    Python
  • License
    Other
  • Created about 10 years ago
  • Updated over 3 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Active-Alchemy is a framework agnostic wrapper for SQLAlchemy that makes it really easy to use by implementing a simple active record like api, while it still uses the db.session underneath. Inspired by Flask-SQLAlchemy

Active-Alchemy

Version 1.x.x*


Active-Alchemy is wrapper around SQLAlchemy that makes it simple to use your models in an active record like manner, while it still uses the SQLAlchemy db.session underneath.

Active-Alchemy was created as solution to use my flask's application's model without the need to use Flask-SQLAlchemy outside of Flask projects.

What you may like about Active-Alchemy:

  • Just by instantiating with ActiveAlchemy(), ActiveAlchemy automatically creates the session, model and everything necessary for SQLAlchemy.
  • It provides easy methods such as query(), create(), update(), delete(), to select, create, update, delete entries respectively.
  • It automatically create a primary key for your table
  • It adds the following columns: id, created_at, updated_at, is_deleted, deleted_at
  • When delete(), it soft deletes the entry so it doesn't get queried. But it still exists in the database. This feature allows you to un-delete an entry
  • It uses Arrow for DateTime
  • DateTime is saved in UTC and uses the ArrowType from the SQLAlchemy-Utils
  • Added some data types: JSONType, EmailType, and the whole SQLAlchemy-Utils Type
  • db.now -> gives you the Arrow UTC type
  • It is still SQLAlchemy. You can access all the SQLAlchemy awesomeness

Quick Overview:

Create the model

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy('sqlite://')

class User(db.Model):
	name = db.Column(db.String(25))
	location = db.Column(db.String(50), default="USA")
	last_access = db.Column(db.Datetime)

Retrieve all records

for user in User.query():
    print(user.name)

Create new record

user = User.create(name="Mardix", location="Moon")

# or

user = User(name="Mardix", location="Moon").save()

Get a record by primary key (id)

user = User.get(1234)

Update record from primary key

user = User.get(1234)
if user:
	user.update(location="Neptune") 

Update record from query iteration

for user in User.query():
	user.update(last_access=db.utcnow());

Soft Delete a record

user = User.get(1234)
if user:
	user.delete() 

Query Records

users = User.query(User.location.distinct())

for user in users:
    ...

Query with filter

all = User.query().filter(User.location == "USA")

for user in users:
    ...

How to use

Install

pip install active_alchemy

Create a connection

The ActiveAlchemy class is used to instantiate a SQLAlchemy connection to a database.

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy(dialect+driver://username:password@host:port/database)

Databases Drivers & DB Connection examples

Active-Alchemy comes with a PyMySQL and PG8000 as drivers for MySQL and PostgreSQL respectively, because they are in pure Python. But you can use other drivers for better performance. SQLite is already built in Python.

SQLite:

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy("sqlite://") # in memory

# or 

db = ActiveAlchemy("sqlite:///foo.db") # DB file

PostgreSql:

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy("postgresql+pg8000://user:password@host:port/dbname")

PyMySQL:

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy("mysql+pymysql://user:password@host:port/dbname")

Active-Alchemy also provides access to all the SQLAlchemy functions from the sqlalchemy and sqlalchemy.orm modules. So you can declare models like the following examples:

Create a Model

To start, create a model class and extends it with db.Model

# mymodel.py

from active_alchemy import ActiveAlchemy

db = ActiveAlchemy("sqlite://")

class MyModel(db.Model):
	name = db.Column(db.String(25))
	is_live = db.Column(db.Boolean, default=False)
	
# Put at the end of the model module to auto create all models
db.create_all()
  • Upon creation of the table, db.Model will add the following columns: id, created_at, upated_at, is_deleted, deleted_at

  • It does an automatic table naming (if no table name is already defined using the __tablename__ property) by using the class name. So, for example, a User model gets a table named user, TodoList becomes todo_list The name will not be plurialized.


Models: db.Model

db.Model extends your model with helpers that turn your model into an active record like model. But underneath, it still uses the db.session

db.Model also adds a few preset columns on the table:

id: The primary key

created_at: Datetime. It contains the creation date of the record

updated_at: Datetime. It is updated whenever the record is updated.

deleted_at: Datetime. Contains the datetime the record was soft-deleted.

is_deleted: Boolean. A flag to set if record is soft-deleted or not

-- About Soft Delete --

By definition, soft-delete marks a record as deleted so it doesn't get queried, but it still exists in the database. To actually delete the record itself, a hard delete must apply.

By default, when a record is deleted, Active-Alchemy actually sets is_deleted to True and excludes it from being queried, and deleted_at is also set. But this happens only when using the method db.Model.delete().

When a record is soft-deleted, you can also undelete a record by doing: db.Model.delete(False)

Now, to completely delete off the table, db.Model.delete(hard_delete=True)

-- Querying with db.Model.query() --

Due to the fact that Active-Alchemy has soft-delete, to query a model without the soft-deleted records, you must query your model by using the all(*args, **kwargs) which returns a db.session.query object for you to apply filter on etc.

-- db.BaseModel --

By default db.Model adds several preset columns on the table, if you don't want to have them in your model, you can use instead db.BaseModel, which still give you access to the methods to query your model.

BaseModel by default assumes that your primary key is id, but it

class MyExistingModel(db.BaseModel):
    __tablename__ = "my_old_table"
    __primary_key__  = "my_pk_id"
    
    my_pk_id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String)
    ...

db.Model Methods Description

query(*args, **kwargs)

To start querying the DB and returns a db.session.query object to filter or apply more conditions.

for user in User.query():
	print(user.login)

By default query() will show only all non-soft-delete records. To display both deleted and non deleted items, add the arg: include_deleted=True

for user in User.query(include_deleted=True):
	print(user.login)

To select columns...

for user in User.query(User.name.distinct(), User.location):
	print(user.login)

To use with filter...

all = User
        .query(User.name.distinct, User.location)
        .order_by(User.updated_at.desc())
        .filter(User.location == "Charlotte")

get(id)

Get one record by id. By default it will query only a record that is not soft-deleted

id = 1234
user = User.get(id)
print(user.id)
print(user.login)

To query a record that has been soft deleted, just set the argument include_deleted=True

id = 234
user = User.get(id, include_deleted=True)

create(**kwargs)

To create/insert new record. Same as init, but just a shortcut to it.

record = User.create(login='abc', passw_hash='hash', profile_id=123)
print (record.login) # -> abc

or you can use the init with save()

record = User(login='abc', passw_hash='hash', profile_id=123).save()
print (record.login) # -> abc

or

record = User(login='abc', passw_hash='hash', profile_id=123)
record.save()
print (record.login) # -> abc

update(**kwargs)

Update an existing record

record = User.get(124)
record.update(login='new_login')
print (record.login) # -> new_login

delete()

To soft delete a record. is_deleted will be set to True and deleted_at datetime will be set

record = User.get(124)
record.delete()
print (record.is_deleted) # -> True

To soft UNdelete a record. is_deleted will be set to False and deleted_at datetime will be None

record = User.get(124)
record.delete(delete=False)
print (record.is_deleted) # -> False

To HARD delete a record. The record will be deleted completely

record = User.get(124)
record.delete(hard_delete=True)

save()

A shortcut to session.add + session.commit()

record = User.get(124)
record.login = "Another one"
record.save()

Method Chaining

For convenience, some method chaining are available

user = User(name="Mardix", location="Charlotte").save()

User.get(12345).update(location="Atlanta")

User.get(345).delete().delete(False).update(location="St. Louis")

Aggegated selects

class Product(db.Model):
	name = db.Column(db.String(250))
	price = db.Column(db.Numeric)
	
price_label = db.func.sum(Product.price).label('price')
results = Product.query(price_label)

With Web Application

In a web application you need to call db.session.remove() after each response, and db.session.rollback() if an error occurs. However, if you are using Flask or other framework that uses the after_request and on_exception decorators, these bindings it is done automatically.

For example using Flask, you can do:

app = Flask(__name__)

db = ActiveAlchemy('sqlite://', app=app)

or

db = ActiveAlchemy()

app = Flask(__name__)

db.init_app(app)

More examples

Many databases, one web app

app = Flask(__name__)
db1 = ActiveAlchemy(URI1, app)
db2 = ActiveAlchemy(URI2, app)

Many web apps, one database

db = ActiveAlchemy(URI1)

app1 = Flask(__name__)
app2 = Flask(__name__)
db.init_app(app1)
db.init_app(app2)

Pagination

All the results can be easily paginated

users = User.paginate(page=2, per_page=20)
print(list(users))  # [User(21), User(22), User(23), ... , User(40)]

The paginator object it's an iterable that returns only the results for that page, so you use it in your templates in the same way than the original result:

{% for item in paginated_items %}
    <li>{{ item.name }}</li>
{% endfor %}

Rendering the pages

Below your results is common that you want it to render the list of pages.

The paginator.pages property is an iterator that returns the page numbers, but sometimes not all of them: if there are more than 11 pages, the result will be one of these, depending of what is the current page:

Skipped page numbers are represented as None.

How many items are displayed can be controlled calling paginator.iter_pages instead.

This is one way how you could render such a pagination in your templates:

{% macro pagination(paginator, endpoint=None, class_='pagination') %}
    {% if not endpoint %}
        {% set endpoint = request.endpoint %}
    {% endif %}
    {% if "page" in kwargs %}
        {% do kwargs.pop("page") %}
    {% endif %}
    <nav>
        <ul class="{{ class_ }}">
          {%- if paginator.has_prev %}
            <li><a href="{{ url_for(endpoint, page=paginator.prev_page_number, **kwargs) }}"
             rel="me prev"><span aria-hidden="true">&laquo;</span></a></li>
          {% else %}
            <li class="disabled"><span><span aria-hidden="true">&laquo;</span></span></li>
          {%- endif %}

          {%- for page in paginator.pages %}
            {% if page %}
              {% if page != paginator.page %}
                <li><a href="{{ url_for(endpoint, page=page, **kwargs) }}"
                 rel="me">{{ page }}</a></li>
              {% else %}
                <li class="active"><span>{{ page }}</span></li>
              {% endif %}
            {% else %}
              <li><span class=ellipsis>โ€ฆ</span></li>
            {% endif %}
          {%- endfor %}

          {%- if paginator.has_next %}
            <li><a href="{{ url_for(endpoint, page=paginator.next_page_number, **kwargs) }}"
             rel="me next">ยป</a></li>
          {% else %}
            <li class="disabled"><span aria-hidden="true">&raquo;</span></li>
          {%- endif %}
        </ul>
    </nav>
{% endmacro %}

Credits:

SQLAlchemy

Flask-SQLAlchemy

SQLAlchemy-Wrapper

Paginator

Arrow

SQLAlchemy-Utils


copyright: 2015-2016

license: MIT, see LICENSE for more details.

More Repositories

1

litedom

A reactive Web Component library to create Custom Element and turns any HTML sections into components
JavaScript
415
star
2

sailor

Sailor is a tiny PaaS to install on your servers/VPS that uses git push to deploy micro-apps, micro-services, sites with SSL, on your own servers or VPS
Python
281
star
3

flask-cloudy

A Flask extension to access, upload, download, save and delete files on cloud storage providers such as: AWS S3, Google Storage, Microsoft Azure, Rackspace Cloudfiles, and even Local file system
Python
244
star
4

propel-legacy

Deploy multiple Flask/Django (or PHP/HTML) applications on a single server
Python
161
star
5

assembly

Assembly is a Pythonic Object-Oriented Web Framework built on Flask, that groups your routes by class
Python
158
star
6

flask-recaptcha

The new Google ReCaptcha implementation for Flask without Flask-WTF.
Python
97
star
7

Voodoo

A Modular MVC Framework
PHP
74
star
8

VoodOrm

VoodOrm is a micro-ORM which functions as both a fluent select query API and a CRUD model class. VoodOrm is built on top of PDO and is well fit for small to mid-sized projects, where the emphasis is on simplicity and rapid development rather than infinite flexibility and features. VoodOrm works easily with table relationship. And offers api that gets SQL out of your way
PHP
49
star
9

Paginator

Paginator is a simple class that allows you to create pagination. It doesn't require any database connection.
PHP
36
star
10

litestate

An ambitiously tiny, gizp ~800b, flux-like library to manage your state
JavaScript
33
star
11

Minero

Minero is a simple bot that helps run crypto mining application based on profitability
Python
12
star
12

ses-mailer

Python
11
star
13

ServerScripts

Server scripts is a collection of scripts that I use on my servers
Shell
11
star
14

pickalotto

PickALotto and Winning!
Python
8
star
15

react-admin

A single page app in ReactJS to create user/admin section
JavaScript
8
star
16

Simplate

Simplate is a simple PHP template engine to seprate application logic and content from its presentation.
PHP
7
star
17

Mocha

Python
6
star
18

reversionup

Python
6
star
19

ThickMustache

An extension of Mustache.php, a logic-less template system
5
star
20

vhost-manager

A module that helps you manage Apache virtual hosts
Python
5
star
21

Paginator.py

Paginator for SQLAlchemy query object, list or iterable
Python
2
star
22

pypi-up

A tool to release package on Pypi, by incrementing the version number. It also git tag the version and push it to the repository
Python
2
star
23

mambo

Python
1
star
24

scrape-utils

Python
1
star
25

sqs-mq

Python
1
star
26

gitremote

Python
1
star
27

Yass

YASS is Yet Another Static Site
Python
1
star
28

propel-push

Python
1
star
29

polybase-server

Shell
1
star
30

geoip-api

An auto-updating API for geoip data.
Rust
1
star
31

mardix.github.com

This is the page for mardix.github.com
1
star
32

s3lify

S3lify, a script to deploy secure (SSL) single page application (SPA) or HTML static site on AWS S3, using S3, Route53, Cloudfront and ACM.
Python
1
star