Revisions database and API implementation

This commit implements the initial design for the revisions
database schema and API.
This commit is contained in:
Felipe Monteiro 2017-07-21 23:00:58 +01:00
parent 3ccd758ac2
commit 0b80b5b4e7
2 changed files with 101 additions and 8 deletions

View File

@ -15,6 +15,7 @@
"""Defines interface for DB access."""
import copy
import datetime
import threading
@ -117,11 +118,82 @@ def document_create(values, session=None):
values = values.copy()
values['doc_metadata'] = values.pop('metadata')
values['schema_version'] = values.pop('schemaVersion')
session = session or get_session()
document = models.Document()
existing_document = document_get(
as_dict=False,
**{c: values[c] for c in models.Document.UNIQUE_CONSTRAINTS})
def _document_changed():
other_document = copy.deepcopy(existing_document)
other_document = other_document.to_dict()
# The document has changed if at least one value in ``values`` differs.
for key, val in values.items():
if val != other_document[key]:
return True
return False
if existing_document:
# Only generate a new revision for the document if anything has
# changed.
if _document_changed():
revision_index = revision_update(
revision_index=existing_document['revision_index'])['id']
values['revision_index'] = revision_index
else:
revision_index = revision_create()['id']
values['revision_index'] = revision_index
document = existing_document or models.Document()
with session.begin():
document.update(values)
document.save(session=session)
return document.to_dict()
def document_get(session=None, as_dict=True, **filters):
unqiue_constraints = models.Document.UNIQUE_CONSTRAINTS
session = session or get_session()
if 'document_id' in filters:
# Get the document by `document_id`.
document = session.query(models.Document).get(filters['document_id'])
elif all([c in filters for c in unqiue_constraints]):
# Get the document by its unique constraints.
document = session.query(models.Document)
document = document.filter_by(**{c: filters[c]
for c in unqiue_constraints}).first()
if as_dict:
return document.to_dict() if document else {}
return document
####################
def revision_create(session=None):
session = session or get_session()
revision = models.Revision()
with session.begin():
revision.save(session=session)
return revision.to_dict()
def revision_update(session=None, revision_index=None):
session = session or get_session()
previous_revision = session.query(models.Revision).get(revision_index)
new_revision = models.Revision()
with session.begin():
# Create the new revision with a reference to the previous revision.
new_revision.update({'previous': revision_index})
new_revision.save(session=session)
# Update the previous revision with a reference to the new revision.
previous_revision.update({'next': new_revision.id})
previous_revision.save(session=session)
return new_revision.to_dict()

View File

@ -22,6 +22,7 @@ from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy.ext import declarative
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import orm
from sqlalchemy import schema
@ -107,17 +108,23 @@ class DeckhandBase(models.ModelBase, models.TimestampMixin):
return d
@staticmethod
def gen_unqiue_contraint(self, *fields):
constraint_name = 'ix_' + self.__class__.__name__.lower() + '_'
for field in fields:
constraint_name = constraint_name + '_%s' % field
return schema.UniqueConstraint(*fields, name=constraint_name)
class Document(BASE, DeckhandBase):
__tablename__ = 'document'
__table_args__ = (schema.UniqueConstraint('schema_version', 'kind',
name='ix_documents_schema_version_kind'),)
UNIQUE_CONSTRAINTS = ('schema_version', 'kind')
__tablename__ = 'documents'
__table_args__ = (DeckhandBase.gen_unqiue_contraint(*UNIQUE_CONSTRAINTS),)
id = Column(String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
# TODO: the revision_index will be a foreign key to a Revision table.
revision_index = Column(String(36), nullable=False,
default=lambda: str(uuid.uuid4()))
revision_index = Column(Integer, ForeignKey('revisions.id'),
nullable=False)
schema_version = Column(String(64), nullable=False)
kind = Column(String(64), nullable=False)
# NOTE: Do not define a maximum length for these JSON data below. However,
@ -127,6 +134,20 @@ class Document(BASE, DeckhandBase):
data = Column(JSONEncodedDict(), nullable=False)
class Revision(BASE, DeckhandBase):
"""Revision history for a ``Document``.
Each ``Revision`` will have a unique ID along with a previous and next
pointer to each ``Revision`` that comprises the revision history for a
``Document``.
"""
__tablename__ = 'revisions'
id = Column(String(36), primary_key=True,
default=lambda: str(uuid.uuid4()))
previous = Column(Integer, ForeignKey('revisions.id'), nullable=True)
next = Column(Integer, ForeignKey('revisions.id'), nullable=True)
def register_models(engine):
"""Create database tables for all models with the given engine."""
models = [Document]