Merge "Extracted DB API layer"

This commit is contained in:
Jenkins 2014-02-14 15:13:50 +00:00 committed by Gerrit Code Review
commit 053fa6d45e
6 changed files with 273 additions and 18 deletions

View File

View 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
View 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

View File

@ -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):

View File

@ -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)

View 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)