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
Mutable
base for customized mutable classes- Support for converting existing classes to mutable classes
Getting Started
Installation
Install and update using pip:
$ pip install -U sqlalchemy-mutable
Setup
- Import classes from sqlalchemy_mutable
from sqlalchemy_mutable import Mutable, MutableType, Query
- Setup the SQLAlchemy session (standard)
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()
class MyModel(Base):
__tablename__ = 'mymodel'
id = Column(Integer, primary_key=True)
greeting = Column(String)
- Initialize a database column with
MutableType
(orMutableListType
orMutableDictType
)
class MyModel(Base):
# ...
mutable = Column(MutableType)
- Add a
query
class attribute initialized with ascoped_session
object (skip this step if using with Flask-SQLAlchemy)
class MyModel(Base):
# ...
query = Query(Session)
- Set
mutable
column toMutable
object
class MyModel(Base):
# ...
def __init__(self):
self.mutable = Mutable()
- Create the database (standard)
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: Mutable
base for customized mutable classes
Users can define custom mutable classes by subclassing Mutable
.
Note: Custom __init__
function must begin by callingsuper().__init__()
.
class CustomMutable(Mutable):
def __init__(self, name='world'):
super().__init__() # Begin by calling super().__init__()
self.name = name
def greeting(self):
return 'hello {}'.format(self.name)
x = MyModel()
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 5.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 by registering it using
@Mutable.register_tracked_type(<Existing Class>)
. - Define
__init__
for the new mutable class.__init__
takes asource
(an instance of the existing class type) and aroot
(theMutable
instance at the root of the nested mutable structure, default toNone
). 3.1. Assign the root withself.root=root
. 3.2. Collect the arguments and keyworks arguments of the existing class constructor fromsource
. 3.3. Callsuper().__init__(root, <arguments>)
where the arguments followingroot
are those you collected in 3.2. This calls the existing class constructor with the collected arguments.
You can now treat the existing class as if it were mutable.
class ExistingClass():
def __init__(self, name):
self.name = name
print('My name is', self.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
def __init__(self, source=(), root=None):
self.root = root
src_name = source.name if hasattr(source, 'name') else None
print('source name is', src_name)
super().__init__(root, name=src_name)
x = MyModel()
session.add(x)
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:
My name is world
source name is world
My name is world
hello world
hello moon
Example 5.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: 1.1._convert(object, root=None)
converts a single object. 1.2._convert_iterable(iterable)
converts iterables likelist
. 1.3._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):
self.root = root
# 1. Convert existing class constructor arguments to Mutable objects
converted_list = self._convert_iterable(source)
super().__init__(root, 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, self.root))
x = MyModel()
x.mutable.nested_list = []
session.commit()
x.mutable.nested_list.append('hello world')
session.commit()
print(x.mutable.nested_list[0])
Outputs:
hello world
Fortunately, I have already defined MutableList
and MutableDict
for you. These classes underlie the functionality in Example 2.
License
This project is licensed under the MIT License LICENSE.
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.1-py3-none-any.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8d9850f5b4f65822ddde8c833cc0d622b08fb79e971b39c3c672cfcec12b9796 |
|
MD5 | 96d2dbbeb07e0e84fcbf9855c7684ffe |
|
BLAKE2b-256 | dc82602f52e8b4409f5d7ab921c74ccf1699f4ce05550b695bc2f9e2e209b5aa |