Skip to main content

SQLAlchemy-Mutable

SQLAlchemy-Mutable provides generic nested mutable objects and iterables for SQLAlchemy. Its primary features are:

  1. Nested mutation tracking
  2. Mutation tracking for iterables (list and dict)
  3. Support for embedded database models
  4. Mutable base for customized mutable classes
  5. Support for converting existing classes to mutable classes

Getting Started

Installation

Install and update using pip:

$ pip install -U sqlalchemy-mutable

Setup

  1. Import classes from sqlalchemy_mutable
from sqlalchemy_mutable import Mutable, MutableType, Query
  1. 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)
  1. Initialize a database column with MutableType (or MutableListType or MutableDictType)
class MyModel(Base):
    # ...
    mutable = Column(MutableType)
  1. Add a query class attribute initialized with a scoped_session object (skip this step if using with Flask-SQLAlchemy)
class MyModel(Base):
    # ...
    query = Query(Session)
  1. Set mutable column to Mutable object
class MyModel(Base):
    # ...
    def __init__(self):
        self.mutable = Mutable()
  1. 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:

  1. Create a new mutable class which inherits from Mutable and the existing class.
  2. Associate the new mutable class with the existing class by registering it using @Mutable.register_tracked_type(<Existing Class>).
  3. Define __init__ for the new mutable class. __init__ takes a source (an instance of the existing class type) and a root (the Mutable instance at the root of the nested mutable structure, default to None). 3.1. Assign the root with self.root=root. 3.2. Collect the arguments and keyworks arguments of the existing class constructor from source. 3.3. Call super().__init__(root, <arguments>) where the arguments following root 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:

  1. Existing class methods take (potentially) mutable arguments. Convert existing class method arguments to Mutable objects before passing to the existing class method with super().<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 like list. 1.3. _convert_mapping(mapping) converts key:value mappings like dict.
  2. 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.
  3. 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 call self._changed() in addition to the existing class method super().<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

Metadata

Release files for sqlalchemy-mutable 0.0.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sqlalchemy-mutable 0.0.1
File Size Uploaded
sqlalchemy-mutable-0.0.1.tar.gz 8.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sqlalchemy-mutable 0.0.1
File Interpreter ABI Platform
sqlalchemy_mutable-0.0.1-py3-none-any.whl Python 3 none any Details

Total release size: 17.4 kB

Release files / sqlalchemy-mutable-0.0.1.tar.gz

Download URL sqlalchemy-mutable-0.0.1.tar.gz
Size 8.0 kB
Tags Source
SHA-256 checksum
How to use checksums
9eb99b8f911ac7aecea09a365e977fd141d8779a875624e72dcdb246637ec645
BLAKE2b-256 checksum
How to use checksums
f1ecf100a45640b800aaa3c32db435b39c92919909261b432c42ae0a876cc9a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.14.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.35.0 CPython/3.7.1

Release files / sqlalchemy_mutable-0.0.1-py3-none-any.whl

Download URL sqlalchemy_mutable-0.0.1-py3-none-any.whl
Size 9.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8d9850f5b4f65822ddde8c833cc0d622b08fb79e971b39c3c672cfcec12b9796
BLAKE2b-256 checksum
How to use checksums
dc82602f52e8b4409f5d7ab921c74ccf1699f4ce05550b695bc2f9e2e209b5aa
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/1.14.0 pkginfo/1.5.0.1 requests/2.21.0 setuptools/41.2.0 requests-toolbelt/0.9.1 tqdm/4.35.0 CPython/3.7.1

Release history Release notifications | RSS feed

1.0.2

2 release files

1.0.1

2 release files

0.0.13

2 release files

0.0.11

2 release files

0.0.10

2 release files

0.0.9

2 release files

0.0.8

2 release files

0.0.7

2 release files

0.0.6

2 release files

0.0.5

2 release files

0.0.4

2 release files

0.0.3

2 release files

0.0.2

2 release files

This release

0.0.1 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page