Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mardix/active-alchemy
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
https://github.com/mardix/active-alchemy
Last synced: 4 days ago
JSON representation
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
- Host: GitHub
- URL: https://github.com/mardix/active-alchemy
- Owner: mardix
- License: other
- Created: 2014-08-24T05:14:27.000Z (about 10 years ago)
- Default Branch: master
- Last Pushed: 2021-03-01T07:02:06.000Z (over 3 years ago)
- Last Synced: 2024-10-11T03:36:49.081Z (about 1 month ago)
- Language: Python
- Homepage:
- Size: 485 KB
- Stars: 121
- Watchers: 8
- Forks: 11
- Open Issues: 10
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG
- License: LICENSE
Awesome Lists containing this project
README
# 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 recordsfor user in User.query():
print(user.name)
#### Create new recorduser = 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 Recordsusers = 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 ActiveAlchemydb = 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) # -> abcor you can use the __init__ with save()
record = User(login='abc', passw_hash='hash', profile_id=123).save()
print (record.login) # -> abc
orrecord = 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 Nonerecord = User.get(124)
record.delete(delete=False)
print (record.is_deleted) # -> False
To HARD delete a record. The record will be deleted completelyrecord = 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 %}
{% 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 %}
- «
- «
- {{ page }}
- {{ page }}
- …
- »
- »
{%- if paginator.has_prev %}
{% else %}
{%- endif %}
{%- for page in paginator.pages %}
{% if page %}
{% if page != paginator.page %}
{% else %}
{% endif %}
{% else %}
{% endif %}
{%- endfor %}
{%- if paginator.has_next %}
{% else %}
{%- endif %}
{% endmacro %}
______
#### Credits:
[SQLAlchemy](http://www.sqlalchemy.org/)
[Flask-SQLAlchemy](https://pythonhosted.org/Flask-SQLAlchemy)
[SQLAlchemy-Wrapper](https://github.com/lucuma/sqlalchemy-wrapper)
[Paginator](https://github.com/mardix/paginator.py)
[Arrow](http://crsmithdev.com/arrow/)
[SQLAlchemy-Utils](https://sqlalchemy-utils.readthedocs.io)
---
copyright: 2015-2016
license: MIT, see LICENSE for more details.