Merge "Extracted DB API layer"
This commit is contained in:
commit
053fa6d45e
0
storyboard/common/__init__.py
Normal file
0
storyboard/common/__init__.py
Normal file
46
storyboard/common/exception.py
Normal file
46
storyboard/common/exception.py
Normal file
@ -0,0 +1,46 @@
|
||||
# Copyright (c) 2014 Mirantis Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
class StoryboardException(Exception):
|
||||
"""Base Exception for the project
|
||||
|
||||
To correctly use this class, inherit from it and define
|
||||
the 'message' property.
|
||||
"""
|
||||
|
||||
message = "An unknown exception occurred"
|
||||
|
||||
def __str__(self):
|
||||
return self.message
|
||||
|
||||
def __init__(self):
|
||||
super(StoryboardException, self).__init__(self.message)
|
||||
|
||||
|
||||
class NotFound(StoryboardException):
|
||||
message = "Object not found"
|
||||
|
||||
def __init__(self, message=None):
|
||||
if message:
|
||||
self.message = message
|
||||
|
||||
|
||||
class DuplicateEntry(StoryboardException):
|
||||
message = "Database object already exists"
|
||||
|
||||
def __init__(self, message=None):
|
||||
if message:
|
||||
self.message = message
|
125
storyboard/db/api.py
Normal file
125
storyboard/db/api.py
Normal file
@ -0,0 +1,125 @@
|
||||
# Copyright (c) 2014 Mirantis Inc.
|
||||
#
|
||||
# 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 oslo.config import cfg
|
||||
|
||||
from storyboard.common import exception as exc
|
||||
from storyboard.db import models
|
||||
from storyboard.openstack.common.db import exception as db_exc
|
||||
from storyboard.openstack.common.db.sqlalchemy import session as db_session
|
||||
from storyboard.openstack.common import log
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
get_session = db_session.get_session
|
||||
|
||||
|
||||
def model_query(model, session=None):
|
||||
"""Query helper.
|
||||
|
||||
:param model: base model to query
|
||||
"""
|
||||
session = session or get_session()
|
||||
query = session.query(model)
|
||||
return query
|
||||
|
||||
|
||||
## BEGIN Projects
|
||||
|
||||
def _project_get(project_id, session):
|
||||
query = model_query(models.Project, session)
|
||||
return query.filter_by(id=project_id).first()
|
||||
|
||||
|
||||
def project_get(project_id):
|
||||
return _project_get(project_id, get_session())
|
||||
|
||||
|
||||
def project_get_all(**kwargs):
|
||||
query = model_query(models.Project)
|
||||
return query.filter_by(**kwargs).all()
|
||||
|
||||
|
||||
def project_create(values):
|
||||
project = models.Project()
|
||||
project.update(values.copy())
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
try:
|
||||
project.save(session=session)
|
||||
except db_exc.DBDuplicateEntry as e:
|
||||
raise exc.DuplicateEntry("Duplicate entry for Project: %s"
|
||||
% e.columns)
|
||||
|
||||
return project
|
||||
|
||||
|
||||
def project_update(project_id, values):
|
||||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
project = _project_get(project_id, session)
|
||||
if project is None:
|
||||
raise exc.NotFound("Project %s not found" % project_id)
|
||||
|
||||
project.update(values.copy())
|
||||
|
||||
return project
|
||||
|
||||
|
||||
## BEGIN Stories
|
||||
|
||||
def _story_get(story_id, session):
|
||||
query = model_query(models.Story, session)
|
||||
return query.filter_by(id=story_id).first()
|
||||
|
||||
|
||||
def story_get_all(**kwargs):
|
||||
query = model_query(models.Story)
|
||||
return query.filter_by(**kwargs).all()
|
||||
|
||||
|
||||
def story_get(story_id):
|
||||
return _story_get(story_id, get_session())
|
||||
|
||||
|
||||
def story_create(values):
|
||||
story = models.Story()
|
||||
story.update(values.copy())
|
||||
|
||||
session = get_session()
|
||||
with session.begin():
|
||||
try:
|
||||
story.save(session)
|
||||
except db_exc.DBDuplicateEntry as e:
|
||||
raise exc.DuplicateEntry("Duplicate etnry for Story: %s"
|
||||
% e.colums)
|
||||
|
||||
return story
|
||||
|
||||
|
||||
def story_update(story_id, values):
|
||||
session = get_session()
|
||||
|
||||
with session.begin():
|
||||
story = _story_get(story_id, session)
|
||||
if story is None:
|
||||
raise exc.NotFound("Story %s not found" % story_id)
|
||||
|
||||
story.update(values.copy())
|
||||
|
||||
return story
|
@ -101,7 +101,23 @@ class TestCase(testtools.TestCase):
|
||||
PATH_PREFIX = '/v1'
|
||||
|
||||
|
||||
class FunctionalTest(TestCase):
|
||||
class DbTestCase(TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(DbTestCase, self).setUp()
|
||||
|
||||
CONF.set_override("connection", "sqlite://", "database")
|
||||
self.init_db_cache()
|
||||
|
||||
@lockutils.synchronized("storyboard", "db_init", True)
|
||||
def init_db_cache(self):
|
||||
global _DB_CACHE
|
||||
if not _DB_CACHE:
|
||||
_DB_CACHE = Database()
|
||||
self.useFixture(_DB_CACHE)
|
||||
|
||||
|
||||
class FunctionalTest(DbTestCase):
|
||||
"""Used for functional tests of Pecan controllers where you need to
|
||||
test your literal application and its integration with the
|
||||
framework.
|
||||
@ -110,18 +126,9 @@ class FunctionalTest(TestCase):
|
||||
def setUp(self):
|
||||
super(FunctionalTest, self).setUp()
|
||||
|
||||
CONF.set_override("connection", "sqlite://", "database")
|
||||
self.init_db_cache()
|
||||
self.app = self._make_app()
|
||||
self.addCleanup(self._reset_pecan)
|
||||
|
||||
@lockutils.synchronized("storyboard", "db_init", True)
|
||||
def init_db_cache(self):
|
||||
global _DB_CACHE
|
||||
if not _DB_CACHE:
|
||||
_DB_CACHE = Database()
|
||||
self.useFixture(_DB_CACHE)
|
||||
|
||||
def _make_app(self):
|
||||
config = {
|
||||
'app': {
|
||||
@ -152,7 +159,6 @@ class FunctionalTest(TestCase):
|
||||
:param path_prefix: prefix of the url path
|
||||
"""
|
||||
full_path = path_prefix + path
|
||||
print('%s: %s %s' % (method.upper(), full_path, params))
|
||||
response = getattr(self.app, "%s_json" % method)(
|
||||
str(full_path),
|
||||
params=params,
|
||||
@ -161,7 +167,6 @@ class FunctionalTest(TestCase):
|
||||
extra_environ=extra_environ,
|
||||
expect_errors=expect_errors
|
||||
)
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def put_json(self, path, params, expect_errors=False, headers=None,
|
||||
@ -232,13 +237,11 @@ class FunctionalTest(TestCase):
|
||||
:param path_prefix: prefix of the url path
|
||||
"""
|
||||
full_path = path_prefix + path
|
||||
print('DELETE: %s' % (full_path))
|
||||
response = self.app.delete(str(full_path),
|
||||
headers=headers,
|
||||
status=status,
|
||||
extra_environ=extra_environ,
|
||||
expect_errors=expect_errors)
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def get_json(self, path, expect_errors=False, headers=None,
|
||||
@ -268,7 +271,6 @@ class FunctionalTest(TestCase):
|
||||
all_params.update(params)
|
||||
if q:
|
||||
all_params.update(query_params)
|
||||
print('GET: %s %r' % (full_path, all_params))
|
||||
response = self.app.get(full_path,
|
||||
params=all_params,
|
||||
headers=headers,
|
||||
@ -276,7 +278,6 @@ class FunctionalTest(TestCase):
|
||||
expect_errors=expect_errors)
|
||||
if not expect_errors:
|
||||
response = response.json
|
||||
print('GOT:%s' % response)
|
||||
return response
|
||||
|
||||
def validate_link(self, link):
|
||||
|
@ -67,6 +67,9 @@ class Database(fixtures.Fixture):
|
||||
def setUp(self):
|
||||
super(Database, self).setUp()
|
||||
|
||||
conn = self.engine.connect()
|
||||
session.get_session()
|
||||
engine = session.get_engine()
|
||||
conn = engine.connect()
|
||||
|
||||
conn.connection.executescript(self._DB)
|
||||
self.addCleanup(self.engine.dispose)
|
||||
self.addCleanup(session.cleanup)
|
||||
|
80
storyboard/tests/db/test_db_api.py
Normal file
80
storyboard/tests/db/test_db_api.py
Normal file
@ -0,0 +1,80 @@
|
||||
# Copyright (c) 2014 Mirantis Inc.
|
||||
#
|
||||
# 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 storyboard.db import api as dbapi
|
||||
from storyboard.tests import base
|
||||
|
||||
|
||||
class ProjectsTest(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ProjectsTest, self).setUp()
|
||||
|
||||
self.project_01 = {
|
||||
'name': u'StoryBoard',
|
||||
'description': u'Awesome Task Tracker'
|
||||
}
|
||||
|
||||
def test_save_project(self):
|
||||
ref = self.project_01
|
||||
saved = dbapi.project_create(ref)
|
||||
|
||||
self.assertIsNotNone(saved.id)
|
||||
self.assertEqual(ref['name'], saved.name)
|
||||
self.assertEqual(ref['description'], saved.description)
|
||||
|
||||
def test_update_project(self):
|
||||
saved = dbapi.project_create(self.project_01)
|
||||
delta = {
|
||||
'name': u'New Name',
|
||||
'description': u'New Description'
|
||||
}
|
||||
updated = dbapi.project_update(saved.id, delta)
|
||||
|
||||
self.assertEqual(saved.id, updated.id)
|
||||
self.assertEqual(delta['name'], updated.name)
|
||||
self.assertEqual(delta['description'], updated.description)
|
||||
|
||||
|
||||
class StoriesTest(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(StoriesTest, self).setUp()
|
||||
|
||||
self.story_01 = {
|
||||
'title': u'Worst Story Ever',
|
||||
'description': u'Story description'
|
||||
}
|
||||
|
||||
def test_create_story(self):
|
||||
ref = self.story_01
|
||||
saved = dbapi.story_create(self.story_01)
|
||||
|
||||
self.assertIsNotNone(saved.id)
|
||||
self.assertEqual(ref['title'], saved.title)
|
||||
self.assertEqual(ref['description'], saved.description)
|
||||
|
||||
def test_update_story(self):
|
||||
saved = dbapi.story_create(self.story_01)
|
||||
delta = {
|
||||
'title': u'New Title',
|
||||
'description': u'New Description'
|
||||
}
|
||||
|
||||
updated = dbapi.story_update(saved.id, delta)
|
||||
|
||||
self.assertEqual(saved.id, updated.id)
|
||||
self.assertEqual(delta['title'], updated.title)
|
||||
self.assertEqual(delta['description'], updated.description)
|
Loading…
x
Reference in New Issue
Block a user