{ "info": { "author": "UNKNOWN", "author_email": "UNKNOWN", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python", "Topic :: Database" ], "description": "================\nsqlalchemy-audit\n================\n\nsqlalchemy-audit provides an easy way to set up revision tracking for your data. It is inspired by SQLAlchemy's ``versioned_history`` example, but uses mapper events instead of session events.\n\n\nExample\n=======\n\nShare your ``DBSession`` with ``Versioned``:\n\n.. code:: python\n\n DBSession = ...\n Versioned.versioned_session(DBSession)\n\nThen simply declare your class as usual and have it inherit ``Versioned``:\n\n.. code:: python\n\n class Reservation(Versioned, Base):\n __tablename__ = 'reservation'\n id = Column(Integer, primary_key=True)\n name = Column(String(50))\n date = Column(Date)\n time = Column(Time)\n party = Column(Integer)\n last_modified = Column(DateTime)\n\n Reservation.broadcast_crud() # todo: handle this automagically\n\n\n.. note:: You can also sub-class ``Versioned`` from your declarative base class.\n\n\nNormal usage remains the same:\n\n.. code:: python\n\n # make new reservation\n steve_reservation = Reservation(name='Steve', \n date=datetime.date(2015, 04, 15),\n time=datetime.time(19, 00),\n party=6)\n session.add(steve_reservation)\n session.commit()\n\n # change reservation to party of 4\n steve_reservation.party = 4\n session.commit()\n\n # cancel the reservation\n session.delete(steve_reservation)\n session.commit()\n\n\nPlus, you could access its revision history.\n\n.. code:: python\n\n >>> DBSession.query(ReservationRev).all()\n [ ReservationRev(rev_id='c74d5bce...', rev_created=1427995346.0, rev_isdelete=False, id=1, name='Steve', date='2015-04-15', time='19:00', party=6, last_modified='2015-04-02 13:22:26.291670'),\n ReservationRev(rev_id='f3f5091d...', rev_created=1428068391.0, rev_isdelete=False, id=1, name='Steve', date='2015-04-15', time='19:00', party=4, last_modified='2015-04-03 09:39:51.098798'),\n ReservationRev(rev_id='3cf1394b...', rev_created=1428534191.0, rev_isdelete=True, id=1, name=None, date=None, time=None, party=None, last_modified=None)\n ]\n\n\nHow it works\n============\n\nSuppose you have a ``reservations`` table.\n\n== ====== ========== ===== ===== ==========================\nid name date time party last_modified\n== ====== ========== ===== ===== ==========================\n 1 Steve 2015-04-15 19:00 4 2015-04-08 13:22:26.291670\n 2 Phil 2015-05-01 18:30 3 2015-04-13 09:38:01.060898\n== ====== ========== ===== ===== ==========================\n\n\nBehind the scenes, we create an revision class ``ReservationRev`` mapped to table ``reservations_rev``. It has the same schema with three additional columns:\n\n rev_id : string (uuid)\n Surrogate key for the revision table.\n\n rev_created : timestamp\n Timestamp (seconds since the epoch as a floating point number) of when the revision was created. (See `Use of rev_created`_.)\n\n rev_isdelete : boolean\n Whether the entry was deleted. (See `Use of rev_isdelete`_.)\n\n\nWhenever you write to the ``reservations`` table, we will insert a new row into the ``reservations_rev`` table. This allows your usage of ``reservations`` to remain unchanged. If need, you could reference the ``reservations_rev`` to get the revision timelime.\n\n\nExample\n-------\n\nFor the following timeline:\n\n- On 2015-04-02, Steve makes a reservation for party of 6 on 2015-04-15 at 19:30.\n- On 2015-04-03, Steve changes the reservation to 4 people.\n- On 2015-04-08, Steve cancels the reservation.\n\n\n``reservations_rev`` will have the following \n\n=========== =============== ============= ====== ====== ========== ====== ====== ==========================\nrev_id rev_created rev_isdelete id name date time party last_modified\n=========== =============== ============= ====== ====== ========== ====== ====== ==========================\nc74d5bce... 1427995346.0 False 1 Steve 2015-04-15 19:00 6 2015-04-02 13:22:26.291670\nf3f5091d... 1428068391.0 False 1 Steve 2015-04-15 19:00 4 2015-04-03 09:39:51.098798\n3cf1394b... 1428534191.0 True 1 (null) (null) (null) (null) (null)\n=========== =============== ============= ====== ====== ========== ====== ====== ==========================\n\n\n\nDesign Decisions\n----------------\n\nWriting to revision table for all writes\n````````````````````````````````````````\n\nThere are several advantages by writing to the revision table for all writes:\n\n 1. complete transaction history in the revision table for easy reads (no joins required)\n 2. complete timeline even if the original table doesn't have a last modified column\n\n\nHowever, this approach has a particular drawback with ``INSERT`` statements with dynamic defaults (such as sequences or auto-datetime). At the time of the insert, the revision table does not have the dynamic values. We recommend the following workarounds:\n\n 1. generate dynamic defaults during object instantiation instead using database defaults\n 2. strictly use client-side defaults in the ORM\n 3. create server-side database triggers to copy values to revision table for inserts\n 4. perform a write-read-write transaction for inserts, which is sub-optimal due to the performance hit\n\n\nUse of rev_created\n``````````````````\n\nTo re-create the revision timeline, we are relying on the use of timestamps. While we recognize there could be clock drift or desynchronization across different servers, there are solutions to these problems. Hence we opt to proceed with timestamp's simplicity.\n\n\nUse of rev_isdelete\n```````````````````\n\nThe ``rev_isdelete`` is a fast and convenient way to determined that a row has been deleted without inspecting the entries. It also allows for entries with all nulls.\n\n\nRequirement of primary/compound keys\n````````````````````````````````````\n\nTBD\n\n\nRequirement of association objects for many-to-many relationships\n`````````````````````````````````````````````````````````````````\n\nTBD", "description_content_type": null, "docs_url": null, "download_url": "UNKNOWN", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/canaryhealth/sqlalchemy_audit", "keywords": null, "license": "MIT", "maintainer": null, "maintainer_email": null, "name": "sqlalchemy_audit", "package_url": "https://pypi.org/project/sqlalchemy_audit/", "platform": "any", "project_url": "https://pypi.org/project/sqlalchemy_audit/", "project_urls": { "Download": "UNKNOWN", "Homepage": "https://github.com/canaryhealth/sqlalchemy_audit" }, "release_url": "https://pypi.org/project/sqlalchemy_audit/0.1.0/", "requires_dist": null, "requires_python": null, "summary": "sqlalchemy-audit provides an easy way to set up revision tracking for your data.", "version": "0.1.0" }, "last_serial": 2498074, "releases": { "0.1.0": [ { "comment_text": "", "digests": { "md5": "63ad094c5a5b42e5d91486b8d0d130a8", "sha256": "54d4e023347c2c45ed5ea3c36502ad00f6329446a9d804e018946b4c430f4756" }, "downloads": -1, "filename": "sqlalchemy_audit-0.1.0.tar.gz", "has_sig": false, "md5_digest": "63ad094c5a5b42e5d91486b8d0d130a8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9230, "upload_time": "2016-12-04T01:27:56", "url": "https://files.pythonhosted.org/packages/66/db/fd271f475bb2657f2ebee612e351ad397791891e86e59aa4c774c9f11878/sqlalchemy_audit-0.1.0.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "63ad094c5a5b42e5d91486b8d0d130a8", "sha256": "54d4e023347c2c45ed5ea3c36502ad00f6329446a9d804e018946b4c430f4756" }, "downloads": -1, "filename": "sqlalchemy_audit-0.1.0.tar.gz", "has_sig": false, "md5_digest": "63ad094c5a5b42e5d91486b8d0d130a8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 9230, "upload_time": "2016-12-04T01:27:56", "url": "https://files.pythonhosted.org/packages/66/db/fd271f475bb2657f2ebee612e351ad397791891e86e59aa4c774c9f11878/sqlalchemy_audit-0.1.0.tar.gz" } ] }