Generic nested mutable objects and iterables for SQLAlchemy
Project description
SQLAlchemy-Mutable
SQLAlchemy-Mutable provides generic nested mutable objects and iterables for SQLAlchemy. Its primary features are:
- Nested mutation tracking
- Mutation tracking for iterables (
list
anddict
) - Support for embedded database models
- Support for common literals
- Support for custom Mutable classes
- Support for converting existing classes to Mutable classes
License
Publications which use this software should include the following citation:
Bowen, D.S. (2019). SQLAlchemy-Mutable [Computer software]. https://github.com/dsbowen/sqlalchemy-mutable
This project is licensed under the MIT License LICENSE.
Getting Started
Installation
Install and update using pip:
$ pip install -U sqlalchemy-mutable
Setup
The following code will get you started with SQLAlchemy-Mutable as quickly as possible:
# 1. Import classes from sqlalchemy_mutable
from sqlalchemy_mutable import Mutable, MutableType, MutableModelBase, Query
# 2. Standard session creation
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker, scoped_session
from sqlalchemy.ext.declarative import declarative_base
engine = create_engine('sqlite:///:memory:')
session_factory = sessionmaker(bind=engine)
Session = scoped_session(session_factory)
session = Session()
Base = declarative_base()
# 3. Subclass MutableModelBase when creating database models
class MyModel(Base, MutableModelBase):
__tablename__ = 'mymodel'
id = Column(Integer, primary_key=True)
greeting = Column(String)
# 4. Initialize a database column with MutableType
mutable = Column(MutableType)
# 5. Add a query class attribute initialized with a scoped_session
query = Query(Session)
def __init__(self):
# 6. Set mutable column to Mutable object
self.mutable = Mutable()
# 7. Create the database
Base.metadata.create_all(engine)
Examples
See full examples with SQLAlchemy and Flask-SQLAlchemy
Example 1: Nested mutation tracking
MutableType
columns track changes made to nested Mutable
objects.
x = MyModel()
session.add(x)
x.mutable.nested_mutable = Mutable()
session.commit()
x.mutable.nested_mutable.greeting = 'hello world'
session.commit()
print(x.mutable.nested_mutable.greeting)
Outputs:
hello world
Example 2: Mutation tracking for iterables
SQLAlchemy-Mutable also supports mutation tracking for nested iterables (list
and dict
)
x = MyModel()
session.add(x)
x.mutable = {'greeting': []}
session.commit()
x.mutable['greeting'].append('hello world')
session.commit()
print(x.mutable['greeting'][0])
Outputs:
hello world
Example 3: Embedded database models
Database models may be embedded in the Mutable
object.
Note: Embedded database models must be flushed or committed before embedding.
x = MyModel()
y = MyModel()
session.add_all([x,y])
session.flush([x,y]) #Flush or commit models before embedding
x.mutable.y = y
session.commit()
y.greeting = 'hello world'
print(x.mutable.y.greeting)
print('Successfully recovered y?', x.mutable.y == y)
Outputs:
hello world
Successfully recovered y? True
Example 4: Set Mutable Columns to common literals and database models
MutableType
columns can take on the values of common Python literals and even other database models.
x = MyModel()
y = MyModel()
session.add_all([x,y])
session.flush([x,y])
x.mutable = 'hello world'
print(x.mutable)
x.mutable = 123
print(x.mutable)
x.mutable = y
session.commit()
y.greeting = 'hello moon'
print(x.mutable)
print(x.mutable.greeting)
print('Successfully recovered y?', x.mutable == y)
Outputs:
hello world
123
<__main__.MyModel object at 0x03924F10>
hello moon
Successfully recovered y? True
Example 5: Custom Mutable classes
Users can define custom mutable classes by subclassing Mutable
.
class CustomMutable(Mutable):
def __init__(self, name='world'):
self.name = name
def greeting(self):
return 'hello {}'.format(self.name)
x = MyModel()
session.add(x)
x.mutable.nested_mutable = CustomMutable()
session.commit()
print(x.mutable.nested_mutable.greeting())
x.mutable.nested_mutable.name = 'moon'
session.commit()
print(x.mutable.nested_mutable.greeting())
Outputs:
hello world
hello moon
Example 6.1: Convert existing classes to mutable classes (basic use)
Users can add mutation tracking to existing classes. The basic steps are:
- Create a new mutable class which inherits from
Mutable
and the existing class. - Associate the new mutable class with the existing class using
@Mutable.register_tracked_type(<Existing Class>)
. - Define the new mutable class constructor.
__init__
takes asource
(an instance of the existing class) and aroot
, whichMutable
will handle. Usesource
to pass arguments to the existing class constructor usingsuper().__init__
.
You can now treat the existing class as if it were mutable.
class ExistingClass():
def __init__(self, name):
self.name = name
def greeting(self):
return 'hello {}'.format(self.name)
# 1. Create new mutable class which inherits from Mutable and ExistingClass
# 2. Registration
@Mutable.register_tracked_type(ExistingClass)
class MutableClass(Mutable, ExistingClass):
# 3. Initialization
# source will be an instance of ExistingClass
def __init__(self, source=None, root=None):
super().__init__(name=source.name)
x = MyModel()
session.add(x)
x.mutable = ExistingClass('')
x.mutable.nested_mutable = ExistingClass('world')
session.commit()
print(x.mutable.nested_mutable.greeting())
x.mutable.nested_mutable.name = 'moon'
session.commit()
print(x.mutable.nested_mutable.greeting())
Outputs:
hello world
hello moon
Example 6.2: Convert existing classes to mutable classes (advanced use)
Notes for converting more complex existing classes to mutable classes:
- Existing class methods take (potentially) mutable arguments. Convert existing class method arguments to
Mutable
objects before passing to the existing class method withsuper().<method>(<converted arguments>)
.Mutable
provides convenience methods for converting arguments:_convert_item(item)
converts a single item._convert_iterable(iterable)
converts iterables likelist
._convert_mapping(mapping)
converts key:value mappings likedict
.
- The existing class contains items other than its attributes whose mutations you want to track. For example, a
list
contains potentially mutable items which are not attributes. In this case, the new mutable class must have a_tracked_items
attribute which lists these items. - The existing class has methods which mutate the object but do not call
__setattr___
,___delattr___
,___setitem___
, or__delitem__
. The new mutable class must redefine these methods to callself._changed()
in addition to the existing class methodsuper().<method>()
.
@Mutable.register_tracked_type(list)
class MutableList(Mutable, list):
def __init__(self, source=[], root=None):
# 1. Convert potentially mutable attributes/items to Mutable objects
converted_list = self._convert_iterable(source)
super().__init__(converted_list)
# 2. Classes with mutable items must have a _tracked_items attribute
# _tracked_items is a list of potentially mutable items
@property
def _tracked_items(self):
return list(self)
# 3. Call self._changed() to register change with the root Mutable object
def append(self, item):
self._changed()
super().append(self._convert_item(item))
x = MyModel()
x.mutable.nested_list = []
db.session.commit()
x.mutable.nested_list.append('hello world')
db.session.commit()
print(x.mutable.nested_list[0])
Outputs:
hello world
Fortunately, I have already defined MutableList
and MutableDict
. These classes underlie the functionality in Example 2.
Acknowledgements
Much inspiration drawn from SQLAlchemy-JSON
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Hashes for sqlalchemy_mutable-0.0.4-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ba5fecc89901944db125a9dba2a6c6b298ad85459fc6e5a60fc07791e70c793c |
|
MD5 | 91b26b18a7c96ef5a9ed340f70d46d6e |
|
BLAKE2b-256 | 93d30a5612d18bac316eac5c0551624fb606d067acb353a271a51fcd6c986d73 |