From 11cf7741cdb9c491ec4f0f926fef56663362bf05 Mon Sep 17 00:00:00 2001 From: Shachar Snapiri Date: Tue, 18 Dec 2018 10:12:28 +0200 Subject: [PATCH] Added create and update hooks to version mixin Objects using the version mixin will now have the version automatically added or updated in the create and update hooks respectively. Change-Id: I198eb89b45f0f16abb97611b8e43c106e24b9b49 Closes-Bug: #1808331 Co-Authored-By: Omer Anson --- doc/source/devrefs/models.rst | 4 +-- doc/source/specs/nb_api_refactor.rst | 1 + dragonflow/db/api_nb.py | 4 ++- dragonflow/db/model_framework.py | 2 +- dragonflow/db/models/core.py | 4 +-- dragonflow/db/models/mixins.py | 10 ++++++ dragonflow/tests/unit/test_mixins.py | 48 ++++++++++++++++++++++++++++ 7 files changed, 67 insertions(+), 6 deletions(-) create mode 100644 dragonflow/tests/unit/test_mixins.py diff --git a/doc/source/devrefs/models.rst b/doc/source/devrefs/models.rst index 30d343eb5..aa66ac5d4 100644 --- a/doc/source/devrefs/models.rst +++ b/doc/source/devrefs/models.rst @@ -233,8 +233,8 @@ director objects better we could define a common class: super(AccessMixin, self).on_create_pre() self.created_at = datetime.datetime.now() - def on_update_pre(self): - super(AccessMixin, self).on_update_pre() + def on_update_pre(self, orig): + super(AccessMixin, self).on_update_pre(orig) self.updated_at = datetime.datetime.now() The above code updates the relevant fields on create/update operations, so if diff --git a/doc/source/specs/nb_api_refactor.rst b/doc/source/specs/nb_api_refactor.rst index 11e4c77de..e23c90732 100644 --- a/doc/source/specs/nb_api_refactor.rst +++ b/doc/source/specs/nb_api_refactor.rst @@ -189,6 +189,7 @@ operations performed in NbApi, e.g. * `on_update_pre` - Called on the object each time it is updated, can be used for example to generate a new version or trigger updates on other objects. + Gets the original object that exists in the database as a parameter. Since those hooks are methods on objects themselves, we can use super() to chain all needed hooks in parents and mixins, according to Python's native MRO. diff --git a/dragonflow/db/api_nb.py b/dragonflow/db/api_nb.py index 859936cf7..2bbe8aeb9 100644 --- a/dragonflow/db/api_nb.py +++ b/dragonflow/db/api_nb.py @@ -14,6 +14,7 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import time import traceback @@ -201,6 +202,7 @@ class NbApi(object): """ model = type(obj) full_obj = self.get(obj) + db_obj = copy.copy(full_obj) if full_obj is None: raise df_exceptions.DBKeyNotFound(key=obj.id) @@ -210,7 +212,7 @@ class NbApi(object): if not changed_fields: return - full_obj.on_update_pre() + full_obj.on_update_pre(db_obj) serialized_obj = full_obj.to_json() topic = _get_topic(full_obj) diff --git a/dragonflow/db/model_framework.py b/dragonflow/db/model_framework.py index 01c951a6a..5eb05b81c 100644 --- a/dragonflow/db/model_framework.py +++ b/dragonflow/db/model_framework.py @@ -156,7 +156,7 @@ class _CommonBase(models.Base): ''' pass - def on_update_pre(self): + def on_update_pre(self, orig): '''Hook function called before object is updated in the NB database. ''' pass diff --git a/dragonflow/db/models/core.py b/dragonflow/db/models/core.py index 5836ebfd3..0306fc784 100644 --- a/dragonflow/db/models/core.py +++ b/dragonflow/db/models/core.py @@ -65,6 +65,6 @@ class Listener(mf.ModelBase): super(Listener, self).on_create_pre() self.update_timestamp() - def on_update_pre(self): - super(Listener, self).on_update_pre() + def on_update_pre(self, orig): + super(Listener, self).on_update_pre(orig) self.update_timestamp() diff --git a/dragonflow/db/models/mixins.py b/dragonflow/db/models/mixins.py index d5a82ebde..7fcd7c247 100644 --- a/dragonflow/db/models/mixins.py +++ b/dragonflow/db/models/mixins.py @@ -36,6 +36,16 @@ class BasicEvents(mf.MixinBase): class Version(mf.MixinBase): version = fields.IntField() + def on_create_pre(self): + super(Version, self).on_create_pre() + if self.version is None: + self.version = 1 + + def on_update_pre(self, orig): + super(Version, self).on_update_pre(orig) + if self.version == orig.version: + self.version += 1 + def is_newer_than(self, other): return other is None or self.version > other.version diff --git a/dragonflow/tests/unit/test_mixins.py b/dragonflow/tests/unit/test_mixins.py new file mode 100644 index 000000000..79d420659 --- /dev/null +++ b/dragonflow/tests/unit/test_mixins.py @@ -0,0 +1,48 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from jsonmodels import fields +import mock +import testtools + +from dragonflow.db import api_nb +import dragonflow.db.model_framework as mf +from dragonflow.db.models import mixins +from dragonflow.tests import base as tests_base +from dragonflow.tests.common import utils + + +@mf.register_model +@mf.construct_nb_db_model +class FieldTestModel(mf.ModelBase, mixins.Version): + table_name = 'test_mixins' + field = fields.IntField() + + +class TestMixinVersions(tests_base.BaseTestCase): + def setUp(self): + super(TestMixinVersions, self).setUp() + self.api_nb = api_nb.NbApi(db_driver=mock.Mock()) + + def test_on_create(self): + instance = FieldTestModel(id='11111111') + self.api_nb.create(instance, True) + self.assertEqual(1, instance.version) + + @testtools.skip('review/480194') + @utils.with_nb_objects( + FieldTestModel(id='11111111', version=1, field=1) + ) + def test_on_update(self): + instance = FieldTestModel(id='11111111', field=2) + self.api_nb.update(instance, True) + db_inst = self.api_nb.get(FieldTestModel(id='11111111')) + self.assertEqual(2, db_inst.version)