{ "info": { "author": "Dillon Bowen", "author_email": "dsbowen@wharton.upenn.edu", "bugtrack_url": null, "classifiers": [ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3" ], "description": "# SQLAlchemy-Mutable\n\nSQLAlchemy-Mutable provides generic nested mutable objects and iterables for [SQLAlchemy](https://www.sqlalchemy.org). Its primary features are:\n\n1. Nested mutation tracking\n2. Mutation tracking for iterables (```list``` and ```dict```)\n3. Support for embedded database models\n4. Support for common literals\n5. Support for custom Mutable classes\n6. Support for converting existing classes to Mutable classes\n\n## License\n\nPublications which use this software should include the following citation:\n\nBowen, D.S. (2019). SQLAlchemy-Mutable \\[Computer software\\]. [https://github.com/dsbowen/sqlalchemy-mutable](https://github.com/dsbowen/sqlalchemy-mutable)\n\nThis project is licensed under the MIT License [LICENSE](https://github.com/dsbowen/sqlalchemy-mutable/blob/master/LICENSE).\n\n## Getting Started\n\n### Installation\n\nInstall and update using [pip](https://pip.pypa.io/en/stable/quickstart):\n\n```\n$ pip install -U sqlalchemy-mutable\n```\n\n### Setup\n\nThe following code will get you started with SQLAlchemy-Mutable as quickly as possible:\n\n```python\n# 1. Import classes from sqlalchemy_mutable\nfrom sqlalchemy_mutable import Mutable, MutableType, MutableModelBase, Query\n\n# 2. Standard session creation\nfrom sqlalchemy import create_engine, Column, Integer, String\nfrom sqlalchemy.orm import sessionmaker, scoped_session\nfrom sqlalchemy.ext.declarative import declarative_base\n\nengine = create_engine('sqlite:///:memory:')\nsession_factory = sessionmaker(bind=engine)\nSession = scoped_session(session_factory)\nsession = Session()\nBase = declarative_base()\n\n# 3. Subclass MutableModelBase when creating database models\nclass MyModel(Base, MutableModelBase):\n __tablename__ = 'mymodel'\n id = Column(Integer, primary_key=True)\n greeting = Column(String)\n\n # 4. Initialize a database column with MutableType\n mutable = Column(MutableType) \n # 5. Add a query class attribute initialized with a scoped_session\n query = Query(Session) \n\n def __init__(self):\n # 6. Set mutable column to Mutable object\n self.mutable = Mutable()\n\n# 7. Create the database\nBase.metadata.create_all(engine)\n```\n\n## Examples\n\nSee full examples with [SQLAlchemy](https://github.com/dsbowen/sqlalchemy-mutable/blob/master/examples.py) and [Flask-SQLAlchemy](https://github.com/dsbowen/sqlalchemy-mutable/blob/master/flask_sqlalchemy_examples.py)\n\n### Example 1: Nested mutation tracking\n\n```MutableType``` columns track changes made to nested ```Mutable``` objects.\n\n```python\nx = MyModel()\nsession.add(x)\nx.mutable.nested_mutable = Mutable()\nsession.commit()\nx.mutable.nested_mutable.greeting = 'hello world'\nsession.commit()\nprint(x.mutable.nested_mutable.greeting)\n```\n\nOutputs:\n\n```\nhello world\n```\n\n### Example 2: Mutation tracking for iterables\n\nSQLAlchemy-Mutable also supports mutation tracking for nested iterables (```list``` and ```dict```)\n\n```python\nx = MyModel()\nsession.add(x)\nx.mutable = {'greeting': []}\nsession.commit()\nx.mutable['greeting'].append('hello world')\nsession.commit()\nprint(x.mutable['greeting'][0])\n```\n\nOutputs:\n\n```\nhello world\n```\n\n### Example 3: Embedded database models\n\nDatabase models may be embedded in the ```Mutable``` object.\n\n**Note:** Embedded database models must be flushed or committed before embedding.\n\n```python\nx = MyModel()\ny = MyModel()\nsession.add_all([x,y])\nsession.flush([x,y]) #Flush or commit models before embedding\nx.mutable.y = y\nsession.commit()\ny.greeting = 'hello world'\nprint(x.mutable.y.greeting)\nprint('Successfully recovered y?', x.mutable.y == y)\n```\n\nOutputs:\n\n```\nhello world\nSuccessfully recovered y? True\n```\n\n### Example 4: Set Mutable Columns to common literals and database models\n\n```MutableType``` columns can take on the values of common Python literals and even other database models.\n\n```python\nx = MyModel()\ny = MyModel()\nsession.add_all([x,y])\nsession.flush([x,y])\nx.mutable = 'hello world'\nprint(x.mutable)\nx.mutable = 123\nprint(x.mutable)\nx.mutable = y\nsession.commit()\ny.greeting = 'hello moon'\nprint(x.mutable)\nprint(x.mutable.greeting)\nprint('Successfully recovered y?', x.mutable == y)\n```\n\nOutputs:\n\n```\nhello world\n123\n<__main__.MyModel object at 0x03924F10>\nhello moon\nSuccessfully recovered y? True\n```\n\n### Example 5: Custom Mutable classes\n\nUsers can define custom mutable classes by subclassing ```Mutable```.\n\n```python\nclass CustomMutable(Mutable):\n def __init__(self, name='world'):\n self.name = name\n\n def greeting(self):\n return 'hello {}'.format(self.name)\n\nx = MyModel()\nsession.add(x)\nx.mutable.nested_mutable = CustomMutable()\nsession.commit()\nprint(x.mutable.nested_mutable.greeting())\nx.mutable.nested_mutable.name = 'moon'\nsession.commit()\nprint(x.mutable.nested_mutable.greeting())\n```\n\nOutputs:\n\n```\nhello world\nhello moon\n```\n\n### Example 6.1: Convert existing classes to mutable classes (basic use)\n\nUsers can add mutation tracking to existing classes. The basic steps are:\n1. Create a new mutable class which inherits from ```Mutable``` and the existing class.\n2. Associate the new mutable class with the existing class using```@Mutable.register_tracked_type()```.\n3. Define the new mutable class constructor. ```__init__``` takes a ```source``` (an instance of the existing class) and a ```root```, which ```Mutable``` will handle. Use ```source``` to pass arguments to the existing class constructor using ```super().__init__```.\n\nYou can now treat the existing class as if it were mutable.\n\n```python\nclass ExistingClass():\n def __init__(self, name):\n self.name = name\n\n def greeting(self):\n return 'hello {}'.format(self.name)\n\n# 1. Create new mutable class which inherits from Mutable and ExistingClass\n# 2. Registration\n@Mutable.register_tracked_type(ExistingClass)\nclass MutableClass(Mutable, ExistingClass):\n # 3. Initialization\n # source will be an instance of ExistingClass\n def __init__(self, source=None, root=None):\n super().__init__(name=source.name)\n\nx = MyModel()\nsession.add(x)\nx.mutable = ExistingClass('')\nx.mutable.nested_mutable = ExistingClass('world')\nsession.commit()\nprint(x.mutable.nested_mutable.greeting())\nx.mutable.nested_mutable.name = 'moon'\nsession.commit()\nprint(x.mutable.nested_mutable.greeting())\n```\n\nOutputs:\n\n```\nhello world\nhello moon\n```\n\n### Example 6.2: Convert existing classes to mutable classes (advanced use)\n\nNotes for converting more complex existing classes to mutable classes:\n1. *Existing class methods take (potentially) mutable arguments*. Convert existing class method arguments to ```Mutable``` objects before passing to the existing class method with ```super().()```. ```Mutable``` provides convenience methods for converting arguments:\n 1. ```_convert_item(item)``` converts a single item.\n 2. ```_convert_iterable(iterable)``` converts iterables like ```list```.\n 3. ```_convert_mapping(mapping)``` converts key:value mappings like ```dict```.\n2. *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.\n3. *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().()```.\n\n```python\n@Mutable.register_tracked_type(list) \nclass MutableList(Mutable, list):\n def __init__(self, source=[], root=None):\n # 1. Convert potentially mutable attributes/items to Mutable objects\n converted_list = self._convert_iterable(source)\n super().__init__(converted_list)\n\n # 2. Classes with mutable items must have a _tracked_items attribute\n # _tracked_items is a list of potentially mutable items\n @property\n def _tracked_items(self):\n return list(self)\n\n # 3. Call self._changed() to register change with the root Mutable object\n def append(self, item):\n self._changed()\n super().append(self._convert_item(item))\n\nx = MyModel()\nx.mutable.nested_list = []\ndb.session.commit()\nx.mutable.nested_list.append('hello world')\ndb.session.commit()\nprint(x.mutable.nested_list[0])\n```\n\nOutputs:\n\n```\nhello world\n```\n\nFortunately, I have already defined ```MutableList``` and ```MutableDict```. These classes underlie the functionality in Example 2.\n\n## Acknowledgements\n\nMuch inspiration drawn from [SQLAlchemy-JSON](https://pypi.org/project/sqlalchemy-json/)\n\n", "description_content_type": "text/markdown", "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/dsbowen/sqlalchemy-mutable", "keywords": "", "license": "", "maintainer": "", "maintainer_email": "", "name": "sqlalchemy-mutable", "package_url": "https://pypi.org/project/sqlalchemy-mutable/", "platform": "", "project_url": "https://pypi.org/project/sqlalchemy-mutable/", "project_urls": { "Homepage": "https://github.com/dsbowen/sqlalchemy-mutable" }, "release_url": "https://pypi.org/project/sqlalchemy-mutable/0.0.4/", "requires_dist": null, "requires_python": ">=3.6", "summary": "Generic nested mutable objects and iterables for SQLAlchemy", "version": "0.0.4" }, "last_serial": 5932607, "releases": { "0.0.1": [ { "comment_text": "", "digests": { "md5": "96d2dbbeb07e0e84fcbf9855c7684ffe", "sha256": "8d9850f5b4f65822ddde8c833cc0d622b08fb79e971b39c3c672cfcec12b9796" }, "downloads": -1, "filename": "sqlalchemy_mutable-0.0.1-py3-none-any.whl", "has_sig": false, "md5_digest": "96d2dbbeb07e0e84fcbf9855c7684ffe", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 9378, "upload_time": "2019-09-12T14:27:16", "url": "https://files.pythonhosted.org/packages/dc/82/602f52e8b4409f5d7ab921c74ccf1699f4ce05550b695bc2f9e2e209b5aa/sqlalchemy_mutable-0.0.1-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3d2fd8f3c6f46f35bf3629d8731f4d4c", "sha256": "9eb99b8f911ac7aecea09a365e977fd141d8779a875624e72dcdb246637ec645" }, "downloads": -1, "filename": "sqlalchemy-mutable-0.0.1.tar.gz", "has_sig": false, "md5_digest": "3d2fd8f3c6f46f35bf3629d8731f4d4c", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 8000, "upload_time": "2019-09-12T14:27:19", "url": "https://files.pythonhosted.org/packages/f1/ec/f100a45640b800aaa3c32db435b39c92919909261b432c42ae0a876cc9a1/sqlalchemy-mutable-0.0.1.tar.gz" } ], "0.0.2": [ { "comment_text": "", "digests": { "md5": "28f8865d92ab77b8103013f386322ca7", "sha256": "dab7e73f4d4767c2819200e497ad7465e37bd9fdcf9b7d0d6a9e8d0d5878607d" }, "downloads": -1, "filename": "sqlalchemy_mutable-0.0.2-py3-none-any.whl", "has_sig": false, "md5_digest": "28f8865d92ab77b8103013f386322ca7", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 9366, "upload_time": "2019-09-12T14:52:23", "url": "https://files.pythonhosted.org/packages/24/f5/c345b3eb6db33e954843c5af7fca69cb2269a0cf88f374fbf39a8d001f30/sqlalchemy_mutable-0.0.2-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "10acec546b14cedf81ee8d683f2c5330", "sha256": "03f79b5f847aee62881f8838b390fa630343d0f9c16bad907eb52402e79aaddf" }, "downloads": -1, "filename": "sqlalchemy-mutable-0.0.2.tar.gz", "has_sig": false, "md5_digest": "10acec546b14cedf81ee8d683f2c5330", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 7988, "upload_time": "2019-09-12T14:52:24", "url": "https://files.pythonhosted.org/packages/59/66/edf495aebc4bc2d52fa9038cb14625a7d586012b5f8f9f07a7f8bba7ad8c/sqlalchemy-mutable-0.0.2.tar.gz" } ], "0.0.3": [ { "comment_text": "", "digests": { "md5": "ca0c4218faeaec448bbb22cc7c50c200", "sha256": "80ee49ff0a45b6b42f44cfe34f3980c929112140e8d1e93ade6ab14ed2fd4340" }, "downloads": -1, "filename": "sqlalchemy_mutable-0.0.3-py3-none-any.whl", "has_sig": false, "md5_digest": "ca0c4218faeaec448bbb22cc7c50c200", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 11266, "upload_time": "2019-09-25T16:46:11", "url": "https://files.pythonhosted.org/packages/30/39/0fe3c5fb331e4f7867e244962c082d7fe06fa66e31c0285e23f5194c4bde/sqlalchemy_mutable-0.0.3-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "1c4a57f22d2340bd7bde64deef78175e", "sha256": "ec8323dace7805c75eb431924fd28f0d5308aa6aefecbc9cb85baeca4e69c44f" }, "downloads": -1, "filename": "sqlalchemy-mutable-0.0.3.tar.gz", "has_sig": false, "md5_digest": "1c4a57f22d2340bd7bde64deef78175e", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11094, "upload_time": "2019-09-25T16:46:14", "url": "https://files.pythonhosted.org/packages/e0/b1/d84cc4153c98128a522ca2eb167f9cb9bf7c3854f6368dd1a3a05aa300df/sqlalchemy-mutable-0.0.3.tar.gz" } ], "0.0.4": [ { "comment_text": "", "digests": { "md5": "91b26b18a7c96ef5a9ed340f70d46d6e", "sha256": "ba5fecc89901944db125a9dba2a6c6b298ad85459fc6e5a60fc07791e70c793c" }, "downloads": -1, "filename": "sqlalchemy_mutable-0.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "91b26b18a7c96ef5a9ed340f70d46d6e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 11480, "upload_time": "2019-10-05T17:17:12", "url": "https://files.pythonhosted.org/packages/93/d3/0a5612d18bac316eac5c0551624fb606d067acb353a271a51fcd6c986d73/sqlalchemy_mutable-0.0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3d05a7782190d88877f4665f2fab0cba", "sha256": "2c153b49ac1d368d61898bb3f81d1a15f35b09fc8695a3cba03991abe75c759c" }, "downloads": -1, "filename": "sqlalchemy-mutable-0.0.4.tar.gz", "has_sig": false, "md5_digest": "3d05a7782190d88877f4665f2fab0cba", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11097, "upload_time": "2019-10-05T17:17:13", "url": "https://files.pythonhosted.org/packages/66/0a/1705a4696a45c44aeed109acf8a8339b4f3525351c03887e23ff7700c3fc/sqlalchemy-mutable-0.0.4.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "91b26b18a7c96ef5a9ed340f70d46d6e", "sha256": "ba5fecc89901944db125a9dba2a6c6b298ad85459fc6e5a60fc07791e70c793c" }, "downloads": -1, "filename": "sqlalchemy_mutable-0.0.4-py3-none-any.whl", "has_sig": false, "md5_digest": "91b26b18a7c96ef5a9ed340f70d46d6e", "packagetype": "bdist_wheel", "python_version": "py3", "requires_python": ">=3.6", "size": 11480, "upload_time": "2019-10-05T17:17:12", "url": "https://files.pythonhosted.org/packages/93/d3/0a5612d18bac316eac5c0551624fb606d067acb353a271a51fcd6c986d73/sqlalchemy_mutable-0.0.4-py3-none-any.whl" }, { "comment_text": "", "digests": { "md5": "3d05a7782190d88877f4665f2fab0cba", "sha256": "2c153b49ac1d368d61898bb3f81d1a15f35b09fc8695a3cba03991abe75c759c" }, "downloads": -1, "filename": "sqlalchemy-mutable-0.0.4.tar.gz", "has_sig": false, "md5_digest": "3d05a7782190d88877f4665f2fab0cba", "packagetype": "sdist", "python_version": "source", "requires_python": ">=3.6", "size": 11097, "upload_time": "2019-10-05T17:17:13", "url": "https://files.pythonhosted.org/packages/66/0a/1705a4696a45c44aeed109acf8a8339b4f3525351c03887e23ff7700c3fc/sqlalchemy-mutable-0.0.4.tar.gz" } ] }