diff options
author | Victor Sergeyev <vsergeyev@mirantis.com> | 2014-04-18 12:02:53 +0300 |
---|---|---|
committer | Victor Sergeyev <vsergeyev@mirantis.com> | 2014-04-18 12:51:26 +0300 |
commit | 276f7570d7af4a7a62d0e1ffb4edf904cfbf0600 (patch) | |
tree | e86806ebe1c11e57557980649e56f1b9f2bcdcf2 | |
parent | e4cfa6d39d2aa53af64ab34de97183f98fbeb667 (diff) | |
download | oslo-db-276f7570d7af4a7a62d0e1ffb4edf904cfbf0600.tar.gz |
Make the tests passing
- add missed requirements
- add required modules from openstack.common
- added entry points to setup.cfg
- fixed tests location
- fixed incorrect common modules imports
38 files changed, 1076 insertions, 69 deletions
diff --git a/.testr.conf b/.testr.conf index fb62267..35d9ba4 100644 --- a/.testr.conf +++ b/.testr.conf @@ -2,6 +2,6 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ - ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE -test_list_option=--list
\ No newline at end of file +test_list_option=--list diff --git a/openstack-common.conf b/openstack-common.conf new file mode 100644 index 0000000..63e8e40 --- /dev/null +++ b/openstack-common.conf @@ -0,0 +1,12 @@ +[DEFAULT] + +# The list of modules to copy from oslo-incubator.git +module=context +module=gettextutils +module=fixture.moxstubout +module=importutils +module=fixture.config +module=timeutils + +# The base module to hold the copy of openstack.common +base=oslo.db diff --git a/oslo/db/api.py b/oslo/db/api.py index 28e9a82..a2a7d0b 100644 --- a/oslo/db/api.py +++ b/oslo/db/api.py @@ -26,8 +26,8 @@ import threading import time from oslo.db import exception -from openstack.common.gettextutils import _LE -from openstack.common import importutils +from oslo.db.openstack.common.gettextutils import _LE +from oslo.db.openstack.common import importutils LOG = logging.getLogger(__name__) diff --git a/oslo/db/exception.py b/oslo/db/exception.py index 601063e..fe9020e 100644 --- a/oslo/db/exception.py +++ b/oslo/db/exception.py @@ -18,7 +18,7 @@ import six -from openstack.common.gettextutils import _ +from oslo.db.openstack.common.gettextutils import _ class DBError(Exception): diff --git a/tests/unit/db/__init__.py b/oslo/db/openstack/__init__.py index e69de29..e69de29 100644 --- a/tests/unit/db/__init__.py +++ b/oslo/db/openstack/__init__.py diff --git a/oslo/db/openstack/common/__init__.py b/oslo/db/openstack/common/__init__.py new file mode 100644 index 0000000..d1223ea --- /dev/null +++ b/oslo/db/openstack/common/__init__.py @@ -0,0 +1,17 @@ +# +# 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. + +import six + + +six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff --git a/oslo/db/openstack/common/context.py b/oslo/db/openstack/common/context.py new file mode 100644 index 0000000..3eeb445 --- /dev/null +++ b/oslo/db/openstack/common/context.py @@ -0,0 +1,111 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +Simple class that stores security context information in the web request. + +Projects should subclass this class if they wish to enhance the request +context or provide additional information in their specific WSGI pipeline. +""" + +import itertools +import uuid + + +def generate_request_id(): + return b'req-' + str(uuid.uuid4()).encode('ascii') + + +class RequestContext(object): + + """Helper class to represent useful information about a request context. + + Stores information about the security context under which the user + accesses the system, as well as additional request information. + """ + + user_idt_format = '{user} {tenant} {domain} {user_domain} {p_domain}' + + def __init__(self, auth_token=None, user=None, tenant=None, domain=None, + user_domain=None, project_domain=None, is_admin=False, + read_only=False, show_deleted=False, request_id=None, + instance_uuid=None): + self.auth_token = auth_token + self.user = user + self.tenant = tenant + self.domain = domain + self.user_domain = user_domain + self.project_domain = project_domain + self.is_admin = is_admin + self.read_only = read_only + self.show_deleted = show_deleted + self.instance_uuid = instance_uuid + if not request_id: + request_id = generate_request_id() + self.request_id = request_id + + def to_dict(self): + user_idt = ( + self.user_idt_format.format(user=self.user or '-', + tenant=self.tenant or '-', + domain=self.domain or '-', + user_domain=self.user_domain or '-', + p_domain=self.project_domain or '-')) + + return {'user': self.user, + 'tenant': self.tenant, + 'domain': self.domain, + 'user_domain': self.user_domain, + 'project_domain': self.project_domain, + 'is_admin': self.is_admin, + 'read_only': self.read_only, + 'show_deleted': self.show_deleted, + 'auth_token': self.auth_token, + 'request_id': self.request_id, + 'instance_uuid': self.instance_uuid, + 'user_identity': user_idt} + + +def get_admin_context(show_deleted=False): + context = RequestContext(None, + tenant=None, + is_admin=True, + show_deleted=show_deleted) + return context + + +def get_context_from_function_and_args(function, args, kwargs): + """Find an arg of type RequestContext and return it. + + This is useful in a couple of decorators where we don't + know much about the function we're wrapping. + """ + + for arg in itertools.chain(kwargs.values(), args): + if isinstance(arg, RequestContext): + return arg + + return None + + +def is_user_context(context): + """Indicates if the request context is a normal user.""" + if not context: + return False + if context.is_admin: + return False + if not context.user_id or not context.project_id: + return False + return True diff --git a/tests/unit/db/sqlalchemy/__init__.py b/oslo/db/openstack/common/fixture/__init__.py index e69de29..e69de29 100644 --- a/tests/unit/db/sqlalchemy/__init__.py +++ b/oslo/db/openstack/common/fixture/__init__.py diff --git a/oslo/db/openstack/common/fixture/config.py b/oslo/db/openstack/common/fixture/config.py new file mode 100644 index 0000000..9489b85 --- /dev/null +++ b/oslo/db/openstack/common/fixture/config.py @@ -0,0 +1,85 @@ +# +# Copyright 2013 Mirantis, Inc. +# Copyright 2013 OpenStack Foundation +# All Rights Reserved. +# +# 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. + +import fixtures +from oslo.config import cfg +import six + + +class Config(fixtures.Fixture): + """Allows overriding configuration settings for the test. + + `conf` will be reset on cleanup. + + """ + + def __init__(self, conf=cfg.CONF): + self.conf = conf + + def setUp(self): + super(Config, self).setUp() + # NOTE(morganfainberg): unregister must be added to cleanup before + # reset is because cleanup works in reverse order of registered items, + # and a reset must occur before unregistering options can occur. + self.addCleanup(self._unregister_config_opts) + self.addCleanup(self.conf.reset) + self._registered_config_opts = {} + + def config(self, **kw): + """Override configuration values. + + The keyword arguments are the names of configuration options to + override and their values. + + If a `group` argument is supplied, the overrides are applied to + the specified configuration option group, otherwise the overrides + are applied to the ``default`` group. + + """ + + group = kw.pop('group', None) + for k, v in six.iteritems(kw): + self.conf.set_override(k, v, group) + + def _unregister_config_opts(self): + for group in self._registered_config_opts: + self.conf.unregister_opts(self._registered_config_opts[group], + group=group) + + def register_opt(self, opt, group=None): + """Register a single option for the test run. + + Options registered in this manner will automatically be unregistered + during cleanup. + + If a `group` argument is supplied, it will register the new option + to that group, otherwise the option is registered to the ``default`` + group. + """ + self.conf.register_opt(opt, group=group) + self._registered_config_opts.setdefault(group, set()).add(opt) + + def register_opts(self, opts, group=None): + """Register multiple options for the test run. + + This works in the same manner as register_opt() but takes a list of + options as the first argument. All arguments will be registered to the + same group if the ``group`` argument is supplied, otherwise all options + will be registered to the ``default`` group. + """ + for opt in opts: + self.register_opt(opt, group=group) diff --git a/oslo/db/openstack/common/fixture/moxstubout.py b/oslo/db/openstack/common/fixture/moxstubout.py new file mode 100644 index 0000000..e4cd025 --- /dev/null +++ b/oslo/db/openstack/common/fixture/moxstubout.py @@ -0,0 +1,43 @@ +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# All Rights Reserved. +# +# 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. + +############################################################################## +############################################################################## +## +## DO NOT MODIFY THIS FILE +## +## This file is being graduated to the oslo.dbtest library. Please make all +## changes there, and only backport critical fixes here. - dhellmann +## +############################################################################## +############################################################################## + +import fixtures +from six.moves import mox + + +class MoxStubout(fixtures.Fixture): + """Deal with code around mox and stubout as a fixture.""" + + def setUp(self): + super(MoxStubout, self).setUp() + # emulate some of the mox stuff, we can't use the metaclass + # because it screws with our generators + self.mox = mox.Mox() + self.stubs = self.mox.stubs + self.addCleanup(self.mox.UnsetStubs) + self.addCleanup(self.mox.VerifyAll) diff --git a/oslo/db/openstack/common/gettextutils.py b/oslo/db/openstack/common/gettextutils.py new file mode 100644 index 0000000..e296d1a --- /dev/null +++ b/oslo/db/openstack/common/gettextutils.py @@ -0,0 +1,449 @@ +# Copyright 2012 Red Hat, Inc. +# Copyright 2013 IBM Corp. +# All Rights Reserved. +# +# 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. + +""" +gettext for openstack-common modules. + +Usual usage in an openstack.common module: + + from oslo.db.openstack.common.gettextutils import _ +""" + +import copy +import functools +import gettext +import locale +from logging import handlers +import os + +from babel import localedata +import six + +_localedir = os.environ.get('oslo.db'.upper() + '_LOCALEDIR') +_t = gettext.translation('oslo.db', localedir=_localedir, fallback=True) + +# We use separate translation catalogs for each log level, so set up a +# mapping between the log level name and the translator. The domain +# for the log level is project_name + "-log-" + log_level so messages +# for each level end up in their own catalog. +_t_log_levels = dict( + (level, gettext.translation('oslo.db' + '-log-' + level, + localedir=_localedir, + fallback=True)) + for level in ['info', 'warning', 'error', 'critical'] +) + +_AVAILABLE_LANGUAGES = {} +USE_LAZY = False + + +def enable_lazy(): + """Convenience function for configuring _() to use lazy gettext + + Call this at the start of execution to enable the gettextutils._ + function to use lazy gettext functionality. This is useful if + your project is importing _ directly instead of using the + gettextutils.install() way of importing the _ function. + """ + global USE_LAZY + USE_LAZY = True + + +def _(msg): + if USE_LAZY: + return Message(msg, domain='oslo.db') + else: + if six.PY3: + return _t.gettext(msg) + return _t.ugettext(msg) + + +def _log_translation(msg, level): + """Build a single translation of a log message + """ + if USE_LAZY: + return Message(msg, domain='oslo.db' + '-log-' + level) + else: + translator = _t_log_levels[level] + if six.PY3: + return translator.gettext(msg) + return translator.ugettext(msg) + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = functools.partial(_log_translation, level='info') +_LW = functools.partial(_log_translation, level='warning') +_LE = functools.partial(_log_translation, level='error') +_LC = functools.partial(_log_translation, level='critical') + + +def install(domain, lazy=False): + """Install a _() function using the given translation domain. + + Given a translation domain, install a _() function using gettext's + install() function. + + The main difference from gettext.install() is that we allow + overriding the default localedir (e.g. /usr/share/locale) using + a translation-domain-specific environment variable (e.g. + NOVA_LOCALEDIR). + + :param domain: the translation domain + :param lazy: indicates whether or not to install the lazy _() function. + The lazy _() introduces a way to do deferred translation + of messages by installing a _ that builds Message objects, + instead of strings, which can then be lazily translated into + any available locale. + """ + if lazy: + # NOTE(mrodden): Lazy gettext functionality. + # + # The following introduces a deferred way to do translations on + # messages in OpenStack. We override the standard _() function + # and % (format string) operation to build Message objects that can + # later be translated when we have more information. + def _lazy_gettext(msg): + """Create and return a Message object. + + Lazy gettext function for a given domain, it is a factory method + for a project/module to get a lazy gettext function for its own + translation domain (i.e. nova, glance, cinder, etc.) + + Message encapsulates a string so that we can translate + it later when needed. + """ + return Message(msg, domain=domain) + + from six import moves + moves.builtins.__dict__['_'] = _lazy_gettext + else: + localedir = '%s_LOCALEDIR' % domain.upper() + if six.PY3: + gettext.install(domain, + localedir=os.environ.get(localedir)) + else: + gettext.install(domain, + localedir=os.environ.get(localedir), + unicode=True) + + +class Message(six.text_type): + """A Message object is a unicode object that can be translated. + + Translation of Message is done explicitly using the translate() method. + For all non-translation intents and purposes, a Message is simply unicode, + and can be treated as such. + """ + + def __new__(cls, msgid, msgtext=None, params=None, + domain='oslo.db', *args): + """Create a new Message object. + + In order for translation to work gettext requires a message ID, this + msgid will be used as the base unicode text. It is also possible + for the msgid and the base unicode text to be different by passing + the msgtext parameter. + """ + # If the base msgtext is not given, we use the default translation + # of the msgid (which is in English) just in case the system locale is + # not English, so that the base text will be in that locale by default. + if not msgtext: + msgtext = Message._translate_msgid(msgid, domain) + # We want to initialize the parent unicode with the actual object that + # would have been plain unicode if 'Message' was not enabled. + msg = super(Message, cls).__new__(cls, msgtext) + msg.msgid = msgid + msg.domain = domain + msg.params = params + return msg + + def translate(self, desired_locale=None): + """Translate this message to the desired locale. + + :param desired_locale: The desired locale to translate the message to, + if no locale is provided the message will be + translated to the system's default locale. + + :returns: the translated message in unicode + """ + + translated_message = Message._translate_msgid(self.msgid, + self.domain, + desired_locale) + if self.params is None: + # No need for more translation + return translated_message + + # This Message object may have been formatted with one or more + # Message objects as substitution arguments, given either as a single + # argument, part of a tuple, or as one or more values in a dictionary. + # When translating this Message we need to translate those Messages too + translated_params = _translate_args(self.params, desired_locale) + + translated_message = translated_message % translated_params + + return translated_message + + @staticmethod + def _translate_msgid(msgid, domain, desired_locale=None): + if not desired_locale: + system_locale = locale.getdefaultlocale() + # If the system locale is not available to the runtime use English + if not system_locale[0]: + desired_locale = 'en_US' + else: + desired_locale = system_locale[0] + + locale_dir = os.environ.get(domain.upper() + '_LOCALEDIR') + lang = gettext.translation(domain, + localedir=locale_dir, + languages=[desired_locale], + fallback=True) + if six.PY3: + translator = lang.gettext + else: + translator = lang.ugettext + + translated_message = translator(msgid) + return translated_message + + def __mod__(self, other): + # When we mod a Message we want the actual operation to be performed + # by the parent class (i.e. unicode()), the only thing we do here is + # save the original msgid and the parameters in case of a translation + params = self._sanitize_mod_params(other) + unicode_mod = super(Message, self).__mod__(params) + modded = Message(self.msgid, + msgtext=unicode_mod, + params=params, + domain=self.domain) + return modded + + def _sanitize_mod_params(self, other): + """Sanitize the object being modded with this Message. + + - Add support for modding 'None' so translation supports it + - Trim the modded object, which can be a large dictionary, to only + those keys that would actually be used in a translation + - Snapshot the object being modded, in case the message is + translated, it will be used as it was when the Message was created + """ + if other is None: + params = (other,) + elif isinstance(other, dict): + # Merge the dictionaries + # Copy each item in case one does not support deep copy. + params = {} + if isinstance(self.params, dict): + for key, val in self.params.items(): + params[key] = self._copy_param(val) + for key, val in other.items(): + params[key] = self._copy_param(val) + else: + params = self._copy_param(other) + return params + + def _copy_param(self, param): + try: + return copy.deepcopy(param) + except Exception: + # Fallback to casting to unicode this will handle the + # python code-like objects that can't be deep-copied + return six.text_type(param) + + def __add__(self, other): + msg = _('Message objects do not support addition.') + raise TypeError(msg) + + def __radd__(self, other): + return self.__add__(other) + + if six.PY2: + def __str__(self): + # NOTE(luisg): Logging in python 2.6 tries to str() log records, + # and it expects specifically a UnicodeError in order to proceed. + msg = _('Message objects do not support str() because they may ' + 'contain non-ascii characters. ' + 'Please use unicode() or translate() instead.') + raise UnicodeError(msg) + + +def get_available_languages(domain): + """Lists the available languages for the given translation domain. + + :param domain: the domain to get languages for + """ + if domain in _AVAILABLE_LANGUAGES: + return copy.copy(_AVAILABLE_LANGUAGES[domain]) + + localedir = '%s_LOCALEDIR' % domain.upper() + find = lambda x: gettext.find(domain, + localedir=os.environ.get(localedir), + languages=[x]) + + # NOTE(mrodden): en_US should always be available (and first in case + # order matters) since our in-line message strings are en_US + language_list = ['en_US'] + # NOTE(luisg): Babel <1.0 used a function called list(), which was + # renamed to locale_identifiers() in >=1.0, the requirements master list + # requires >=0.9.6, uncapped, so defensively work with both. We can remove + # this check when the master list updates to >=1.0, and update all projects + list_identifiers = (getattr(localedata, 'list', None) or + getattr(localedata, 'locale_identifiers')) + locale_identifiers = list_identifiers() + + for i in locale_identifiers: + if find(i) is not None: + language_list.append(i) + + # NOTE(luisg): Babel>=1.0,<1.3 has a bug where some OpenStack supported + # locales (e.g. 'zh_CN', and 'zh_TW') aren't supported even though they + # are perfectly legitimate locales: + # https://github.com/mitsuhiko/babel/issues/37 + # In Babel 1.3 they fixed the bug and they support these locales, but + # they are still not explicitly "listed" by locale_identifiers(). + # That is why we add the locales here explicitly if necessary so that + # they are listed as supported. + aliases = {'zh': 'zh_CN', + 'zh_Hant_HK': 'zh_HK', + 'zh_Hant': 'zh_TW', + 'fil': 'tl_PH'} + for (locale, alias) in six.iteritems(aliases): + if locale in language_list and alias not in language_list: + language_list.append(alias) + + _AVAILABLE_LANGUAGES[domain] = language_list + return copy.copy(language_list) + + +def translate(obj, desired_locale=None): + """Gets the translated unicode representation of the given object. + + If the object is not translatable it is returned as-is. + If the locale is None the object is translated to the system locale. + + :param obj: the object to translate + :param desired_locale: the locale to translate the message to, if None the + default system locale will be used + :returns: the translated object in unicode, or the original object if + it could not be translated + """ + message = obj + if not isinstance(message, Message): + # If the object to translate is not already translatable, + # let's first get its unicode representation + message = six.text_type(obj) + if isinstance(message, Message): + # Even after unicoding() we still need to check if we are + # running with translatable unicode before translating + return message.translate(desired_locale) + return obj + + +def _translate_args(args, desired_locale=None): + """Translates all the translatable elements of the given arguments object. + + This method is used for translating the translatable values in method + arguments which include values of tuples or dictionaries. + If the object is not a tuple or a dictionary the object itself is + translated if it is translatable. + + If the locale is None the object is translated to the system locale. + + :param args: the args to translate + :param desired_locale: the locale to translate the args to, if None the + default system locale will be used + :returns: a new args object with the translated contents of the original + """ + if isinstance(args, tuple): + return tuple(translate(v, desired_locale) for v in args) + if isinstance(args, dict): + translated_dict = {} + for (k, v) in six.iteritems(args): + translated_v = translate(v, desired_locale) + translated_dict[k] = translated_v + return translated_dict + return translate(args, desired_locale) + + +class TranslationHandler(handlers.MemoryHandler): + """Handler that translates records before logging them. + + The TranslationHandler takes a locale and a target logging.Handler object + to forward LogRecord objects to after translating them. This handler + depends on Message objects being logged, instead of regular strings. + + The handler can be configured declaratively in the logging.conf as follows: + + [handlers] + keys = translatedlog, translator + + [handler_translatedlog] + class = handlers.WatchedFileHandler + args = ('/var/log/api-localized.log',) + formatter = context + + [handler_translator] + class = openstack.common.log.TranslationHandler + target = translatedlog + args = ('zh_CN',) + + If the specified locale is not available in the system, the handler will + log in the default locale. + """ + + def __init__(self, locale=None, target=None): + """Initialize a TranslationHandler + + :param locale: locale to use for translating messages + :param target: logging.Handler object to forward + LogRecord objects to after translation + """ + # NOTE(luisg): In order to allow this handler to be a wrapper for + # other handlers, such as a FileHandler, and still be able to + # configure it using logging.conf, this handler has to extend + # MemoryHandler because only the MemoryHandlers' logging.conf + # parsing is implemented such that it accepts a target handler. + handlers.MemoryHandler.__init__(self, capacity=0, target=target) + self.locale = locale + + def setFormatter(self, fmt): + self.target.setFormatter(fmt) + + def emit(self, record): + # We save the message from the original record to restore it + # after translation, so other handlers are not affected by this + original_msg = record.msg + original_args = record.args + + try: + self._translate_and_log_record(record) + finally: + record.msg = original_msg + record.args = original_args + + def _translate_and_log_record(self, record): + record.msg = translate(record.msg, self.locale) + + # In addition to translating the message, we also need to translate + # arguments that were passed to the log method that were not part + # of the main message e.g., log.info(_('Some message %s'), this_one)) + record.args = _translate_args(record.args, self.locale) + + self.target.emit(record) diff --git a/oslo/db/openstack/common/importutils.py b/oslo/db/openstack/common/importutils.py new file mode 100644 index 0000000..f40a843 --- /dev/null +++ b/oslo/db/openstack/common/importutils.py @@ -0,0 +1,73 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +Import related utilities and helper functions. +""" + +import sys +import traceback + + +def import_class(import_str): + """Returns a class from a string including module and class.""" + mod_str, _sep, class_str = import_str.rpartition('.') + try: + __import__(mod_str) + return getattr(sys.modules[mod_str], class_str) + except (ValueError, AttributeError): + raise ImportError('Class %s cannot be found (%s)' % + (class_str, + traceback.format_exception(*sys.exc_info()))) + + +def import_object(import_str, *args, **kwargs): + """Import a class and return an instance of it.""" + return import_class(import_str)(*args, **kwargs) + + +def import_object_ns(name_space, import_str, *args, **kwargs): + """Tries to import object from default namespace. + + Imports a class and return an instance of it, first by trying + to find the class in a default namespace, then failing back to + a full path if not found in the default namespace. + """ + import_value = "%s.%s" % (name_space, import_str) + try: + return import_class(import_value)(*args, **kwargs) + except ImportError: + return import_class(import_str)(*args, **kwargs) + + +def import_module(import_str): + """Import a module.""" + __import__(import_str) + return sys.modules[import_str] + + +def import_versioned_module(version, submodule=None): + module = 'oslo.db.v%s' % version + if submodule: + module = '.'.join((module, submodule)) + return import_module(module) + + +def try_import(import_str, default=None): + """Try to import a module and if it fails return default.""" + try: + return import_module(import_str) + except ImportError: + return default diff --git a/oslo/db/openstack/common/timeutils.py b/oslo/db/openstack/common/timeutils.py new file mode 100644 index 0000000..52688a0 --- /dev/null +++ b/oslo/db/openstack/common/timeutils.py @@ -0,0 +1,210 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# 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. + +""" +Time related utilities and helper functions. +""" + +import calendar +import datetime +import time + +import iso8601 +import six + + +# ISO 8601 extended time format with microseconds +_ISO8601_TIME_FORMAT_SUBSECOND = '%Y-%m-%dT%H:%M:%S.%f' +_ISO8601_TIME_FORMAT = '%Y-%m-%dT%H:%M:%S' +PERFECT_TIME_FORMAT = _ISO8601_TIME_FORMAT_SUBSECOND + + +def isotime(at=None, subsecond=False): + """Stringify time in ISO 8601 format.""" + if not at: + at = utcnow() + st = at.strftime(_ISO8601_TIME_FORMAT + if not subsecond + else _ISO8601_TIME_FORMAT_SUBSECOND) + tz = at.tzinfo.tzname(None) if at.tzinfo else 'UTC' + st += ('Z' if tz == 'UTC' else tz) + return st + + +def parse_isotime(timestr): + """Parse time from ISO 8601 format.""" + try: + return iso8601.parse_date(timestr) + except iso8601.ParseError as e: + raise ValueError(six.text_type(e)) + except TypeError as e: + raise ValueError(six.text_type(e)) + + +def strtime(at=None, fmt=PERFECT_TIME_FORMAT): + """Returns formatted utcnow.""" + if not at: + at = utcnow() + return at.strftime(fmt) + + +def parse_strtime(timestr, fmt=PERFECT_TIME_FORMAT): + """Turn a formatted time back into a datetime.""" + return datetime.datetime.strptime(timestr, fmt) + + +def normalize_time(timestamp): + """Normalize time in arbitrary timezone to UTC naive object.""" + offset = timestamp.utcoffset() + if offset is None: + return timestamp + return timestamp.replace(tzinfo=None) - offset + + +def is_older_than(before, seconds): + """Return True if before is older than seconds.""" + if isinstance(before, six.string_types): + before = parse_strtime(before).replace(tzinfo=None) + else: + before = before.replace(tzinfo=None) + + return utcnow() - before > datetime.timedelta(seconds=seconds) + + +def is_newer_than(after, seconds): + """Return True if after is newer than seconds.""" + if isinstance(after, six.string_types): + after = parse_strtime(after).replace(tzinfo=None) + else: + after = after.replace(tzinfo=None) + + return after - utcnow() > datetime.timedelta(seconds=seconds) + + +def utcnow_ts(): + """Timestamp version of our utcnow function.""" + if utcnow.override_time is None: + # NOTE(kgriffs): This is several times faster + # than going through calendar.timegm(...) + return int(time.time()) + + return calendar.timegm(utcnow().timetuple()) + + +def utcnow(): + """Overridable version of utils.utcnow.""" + if utcnow.override_time: + try: + return utcnow.override_time.pop(0) + except AttributeError: + return utcnow.override_time + return datetime.datetime.utcnow() + + +def iso8601_from_timestamp(timestamp): + """Returns a iso8601 formatted date from timestamp.""" + return isotime(datetime.datetime.utcfromtimestamp(timestamp)) + + +utcnow.override_time = None + + +def set_time_override(override_time=None): + """Overrides utils.utcnow. + + Make it return a constant time or a list thereof, one at a time. + + :param override_time: datetime instance or list thereof. If not + given, defaults to the current UTC time. + """ + utcnow.override_time = override_time or datetime.datetime.utcnow() + + +def advance_time_delta(timedelta): + """Advance overridden time using a datetime.timedelta.""" + assert(not utcnow.override_time is None) + try: + for dt in utcnow.override_time: + dt += timedelta + except TypeError: + utcnow.override_time += timedelta + + +def advance_time_seconds(seconds): + """Advance overridden time by seconds.""" + advance_time_delta(datetime.timedelta(0, seconds)) + + +def clear_time_override(): + """Remove the overridden time.""" + utcnow.override_time = None + + +def marshall_now(now=None): + """Make an rpc-safe datetime with microseconds. + + Note: tzinfo is stripped, but not required for relative times. + """ + if not now: + now = utcnow() + return dict(day=now.day, month=now.month, year=now.year, hour=now.hour, + minute=now.minute, second=now.second, + microsecond=now.microsecond) + + +def unmarshall_time(tyme): + """Unmarshall a datetime dict.""" + return datetime.datetime(day=tyme['day'], + month=tyme['month'], + year=tyme['year'], + hour=tyme['hour'], + minute=tyme['minute'], + second=tyme['second'], + microsecond=tyme['microsecond']) + + +def delta_seconds(before, after): + """Return the difference between two timing objects. + + Compute the difference in seconds between two date, time, or + datetime objects (as a float, to microsecond resolution). + """ + delta = after - before + return total_seconds(delta) + + +def total_seconds(delta): + """Return the total seconds of datetime.timedelta object. + + Compute total seconds of datetime.timedelta, datetime.timedelta + doesn't have method total_seconds in Python2.6, calculate it manually. + """ + try: + return delta.total_seconds() + except AttributeError: + return ((delta.days * 24 * 3600) + delta.seconds + + float(delta.microseconds) / (10 ** 6)) + + +def is_soon(dt, window): + """Determines if time is going to happen in the next window seconds. + + :param dt: the time + :param window: minimum seconds to remain to consider the time not soon + + :return: True if expiration is within the given duration + """ + soon = (utcnow() + datetime.timedelta(seconds=window)) + return normalize_time(dt) <= soon diff --git a/oslo/db/sqlalchemy/migration.py b/oslo/db/sqlalchemy/migration.py index 5c69d27..72e1869 100644 --- a/oslo/db/sqlalchemy/migration.py +++ b/oslo/db/sqlalchemy/migration.py @@ -51,7 +51,7 @@ import sqlalchemy from sqlalchemy.schema import UniqueConstraint from oslo.db import exception -from openstack.common.gettextutils import _ +from oslo.db.openstack.common.gettextutils import _ def _get_unique_constraints(self, table): diff --git a/oslo/db/sqlalchemy/migration_cli/ext_migrate.py b/oslo/db/sqlalchemy/migration_cli/ext_migrate.py index cf5280b..bdd86c3 100644 --- a/oslo/db/sqlalchemy/migration_cli/ext_migrate.py +++ b/oslo/db/sqlalchemy/migration_cli/ext_migrate.py @@ -13,10 +13,10 @@ import logging import os +from oslo.db.openstack.common.gettextutils import _LE from oslo.db.sqlalchemy import migration from oslo.db.sqlalchemy.migration_cli import ext_base from oslo.db.sqlalchemy import session as db_session -from openstack.common.gettextutils import _LE LOG = logging.getLogger(__name__) diff --git a/oslo/db/sqlalchemy/migration_cli/manager.py b/oslo/db/sqlalchemy/migration_cli/manager.py index ccc2712..ee0cb5b 100644 --- a/oslo/db/sqlalchemy/migration_cli/manager.py +++ b/oslo/db/sqlalchemy/migration_cli/manager.py @@ -13,7 +13,7 @@ from stevedore import enabled -MIGRATION_NAMESPACE = 'openstack.common.migration' +MIGRATION_NAMESPACE = 'oslo.db.migration' def check_plugin_enabled(ext): diff --git a/oslo/db/sqlalchemy/models.py b/oslo/db/sqlalchemy/models.py index d52edcd..f30e9e3 100644 --- a/oslo/db/sqlalchemy/models.py +++ b/oslo/db/sqlalchemy/models.py @@ -26,7 +26,7 @@ from sqlalchemy import Column, Integer from sqlalchemy import DateTime from sqlalchemy.orm import object_mapper -from openstack.common import timeutils +from oslo.db.openstack.common import timeutils class ModelBase(six.Iterator): diff --git a/oslo/db/sqlalchemy/session.py b/oslo/db/sqlalchemy/session.py index 90ff934..9bf4b6d 100644 --- a/oslo/db/sqlalchemy/session.py +++ b/oslo/db/sqlalchemy/session.py @@ -291,8 +291,8 @@ from sqlalchemy.pool import NullPool, StaticPool from sqlalchemy.sql.expression import literal_column from oslo.db import exception -from openstack.common.gettextutils import _LE, _LW -from openstack.common import timeutils +from oslo.db.openstack.common.gettextutils import _LE, _LW +from oslo.db.openstack.common import timeutils LOG = logging.getLogger(__name__) diff --git a/oslo/db/sqlalchemy/test_migrations.py b/oslo/db/sqlalchemy/test_migrations.py index 886bb04..661b0a7 100644 --- a/oslo/db/sqlalchemy/test_migrations.py +++ b/oslo/db/sqlalchemy/test_migrations.py @@ -26,8 +26,8 @@ from six.moves.urllib import parse import sqlalchemy import sqlalchemy.exc +from oslo.db.openstack.common.gettextutils import _LE from oslo.db.sqlalchemy import utils -from openstack.common.gettextutils import _LE LOG = logging.getLogger(__name__) diff --git a/oslo/db/sqlalchemy/utils.py b/oslo/db/sqlalchemy/utils.py index 02daea5..a71305d 100644 --- a/oslo/db/sqlalchemy/utils.py +++ b/oslo/db/sqlalchemy/utils.py @@ -36,10 +36,10 @@ from sqlalchemy import String from sqlalchemy import Table from sqlalchemy.types import NullType -from openstack.common import context as request_context +from oslo.db.openstack.common import context as request_context +from oslo.db.openstack.common.gettextutils import _, _LI, _LW +from oslo.db.openstack.common import timeutils from oslo.db.sqlalchemy import models -from openstack.common.gettextutils import _, _LI, _LW -from openstack.common import timeutils LOG = logging.getLogger(__name__) diff --git a/requirements.txt b/requirements.txt index dbb4dd1..0fde1f8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,8 @@ -Babel>=0.9.6
\ No newline at end of file +alembic>=0.4.1 +Babel>=0.9.6 +iso8601>=0.1.9 +lockfile>=0.8 +oslo.config>=1.2.0 +SQLAlchemy>=0.7.8,<=0.9.99 +sqlalchemy-migrate>=0.8.2,!=0.8.4 +stevedore>=0.14 @@ -26,6 +26,14 @@ packages = namespace_packages = oslo +[entry_points] +oslo.config.opts = + oslo.db = oslo.db.options:list_opts + +oslo.db.migration = + alembic = oslo.db.sqlalchemy.migration_cli.ext_alembic:AlembicExtension + migrate = oslo.db.sqlalchemy.migration_cli.ext_migrate:MigrateExtension + [build_sphinx] source-dir = doc/source build-dir = doc/build @@ -46,4 +54,4 @@ input_file = oslo.db/locale/oslo.db.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg -output_file = oslo.db/locale/oslo.db.pot
\ No newline at end of file +output_file = oslo.db/locale/oslo.db.pot diff --git a/test-requirements.txt b/test-requirements.txt index 2a46bd8..d7a1da2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -3,9 +3,11 @@ hacking>=0.5.6,<0.8 coverage>=3.6 discover fixtures>=0.3.14 +MySQL-python python-subunit sphinx>=1.1.2 oslosphinx +oslotest testrepository>=0.0.17 testscenarios>=0.4,<0.5 -testtools>=0.9.32
\ No newline at end of file +testtools>=0.9.32 diff --git a/tests/__init__.py b/tests/__init__.py index f88664e..e69de29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,13 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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.
\ No newline at end of file diff --git a/tests/base.py b/tests/base.py index f9a09a8..a3069ed 100644 --- a/tests/base.py +++ b/tests/base.py @@ -24,6 +24,7 @@ _TRUE_VALUES = ('true', '1', 'yes') # FIXME(dhellmann) Update this to use oslo.test library + class TestCase(testtools.TestCase): """Test case base class for all unit tests.""" @@ -51,4 +52,4 @@ class TestCase(testtools.TestCase): stderr = self.useFixture(fixtures.StringStream('stderr')).stream self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) - self.log_fixture = self.useFixture(fixtures.FakeLogger())
\ No newline at end of file + self.log_fixture = self.useFixture(fixtures.FakeLogger()) diff --git a/tests/sqlalchemy/__init__.py b/tests/sqlalchemy/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tests/sqlalchemy/__init__.py diff --git a/tests/unit/db/sqlalchemy/test_migrate.py b/tests/sqlalchemy/test_migrate.py index 23833e1..23833e1 100644 --- a/tests/unit/db/sqlalchemy/test_migrate.py +++ b/tests/sqlalchemy/test_migrate.py diff --git a/tests/unit/db/sqlalchemy/test_migrate_cli.py b/tests/sqlalchemy/test_migrate_cli.py index f23ee87..f23ee87 100644 --- a/tests/unit/db/sqlalchemy/test_migrate_cli.py +++ b/tests/sqlalchemy/test_migrate_cli.py diff --git a/tests/unit/db/sqlalchemy/test_migration_common.py b/tests/sqlalchemy/test_migration_common.py index 9f05aff..9f05aff 100644 --- a/tests/unit/db/sqlalchemy/test_migration_common.py +++ b/tests/sqlalchemy/test_migration_common.py diff --git a/tests/unit/db/sqlalchemy/test_migrations.py b/tests/sqlalchemy/test_migrations.py index f39bb73..f39bb73 100644 --- a/tests/unit/db/sqlalchemy/test_migrations.py +++ b/tests/sqlalchemy/test_migrations.py diff --git a/tests/unit/db/sqlalchemy/test_models.py b/tests/sqlalchemy/test_models.py index 8c23da8..8c23da8 100644 --- a/tests/unit/db/sqlalchemy/test_models.py +++ b/tests/sqlalchemy/test_models.py diff --git a/tests/unit/db/sqlalchemy/test_options.py b/tests/sqlalchemy/test_options.py index 10d6451..4870ed8 100644 --- a/tests/unit/db/sqlalchemy/test_options.py +++ b/tests/sqlalchemy/test_options.py @@ -13,7 +13,7 @@ from oslo.config import cfg -from openstack.common.fixture import config +from oslo.db.openstack.common.fixture import config from tests import utils as test_utils diff --git a/tests/unit/db/sqlalchemy/test_sqlalchemy.py b/tests/sqlalchemy/test_sqlalchemy.py index 9cda622..9cda622 100644 --- a/tests/unit/db/sqlalchemy/test_sqlalchemy.py +++ b/tests/sqlalchemy/test_sqlalchemy.py diff --git a/tests/unit/db/sqlalchemy/test_utils.py b/tests/sqlalchemy/test_utils.py index d7f1783..e4704fb 100644 --- a/tests/unit/db/sqlalchemy/test_utils.py +++ b/tests/sqlalchemy/test_utils.py @@ -33,12 +33,12 @@ from sqlalchemy.sql import select from sqlalchemy.types import UserDefinedType, NullType from oslo.db import exception +from oslo.db.openstack.common.fixture import moxstubout from oslo.db.sqlalchemy import migration from oslo.db.sqlalchemy import models from oslo.db.sqlalchemy import session from oslo.db.sqlalchemy import test_migrations from oslo.db.sqlalchemy import utils -from openstack.common.fixture import moxstubout from tests import utils as test_utils diff --git a/tests/unit/db/test_api.py b/tests/test_api.py index c4b2bb9..5534757 100644 --- a/tests/unit/db/test_api.py +++ b/tests/test_api.py @@ -19,7 +19,7 @@ import mock from oslo.db import api from oslo.db import exception -from openstack.common import importutils +from oslo.db.openstack.common import importutils from tests import utils as test_utils sqla = importutils.import_module('sqlalchemy') @@ -63,7 +63,7 @@ class DBAPI(object): class DBAPITestCase(test_utils.BaseTestCase): def test_dbapi_full_path_module_method(self): - dbapi = api.DBAPI('tests.unit.db.test_api') + dbapi = api.DBAPI('tests.test_api') result = dbapi.api_class_call1(1, 2, kwarg1='meow') expected = ((1, 2), {'kwarg1': 'meow'}) self.assertEqual(expected, result) @@ -72,7 +72,7 @@ class DBAPITestCase(test_utils.BaseTestCase): self.assertRaises(ImportError, api.DBAPI, 'tests.unit.db.not_existent') def test_dbapi_lazy_loading(self): - dbapi = api.DBAPI('tests.unit.db.test_api', lazy=True) + dbapi = api.DBAPI('tests.test_api', lazy=True) self.assertIsNone(dbapi._backend) dbapi.api_class_call1(1, 'abc') diff --git a/tests/test_db.py b/tests/test_db.py deleted file mode 100644 index 6156b0e..0000000 --- a/tests/test_db.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- - -# 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. - -""" -test_db ----------------------------------- - -Tests for `db` module. -""" - -from . import base - - -class TestDb(base.TestCase): - - def test_something(self): - pass
\ No newline at end of file diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..93571cc --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,29 @@ +# Copyright 2010-2011 OpenStack Foundation +# All Rights Reserved. +# +# 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 oslotest import base as test_base + +from oslo.db.openstack.common.fixture import moxstubout + + +class BaseTestCase(test_base.BaseTestCase): + def setUp(self, conf=cfg.CONF): + super(BaseTestCase, self).setUp() + moxfixture = self.useFixture(moxstubout.MoxStubout()) + self.mox = moxfixture.mox + self.stubs = moxfixture.stubs + self.conf = conf + self.addCleanup(self.conf.reset) @@ -1,6 +1,6 @@ [tox] minversion = 1.6 -envlist = py26,py27,py33,pypy,pep8 +envlist = py26,py27,pep8 # NOTE(dhellmann): We cannot set skipdist=True # for oslo libraries because of the namespace package. #skipsdist = True @@ -12,6 +12,7 @@ envlist = py26,py27,py33,pypy,pep8 install_command = pip install -U {opts} {packages} setenv = VIRTUAL_ENV={envdir} + OSLO_LOCK_PATH=/tmp/ deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = python setup.py testr --slowest --testr-args='{posargs}' @@ -23,13 +24,13 @@ commands = flake8 commands = {posargs} [testenv:cover] -commands = python setup.py testr --coverage --testr-args='{posargs}' +commands = OSLO_LOCK_PATH=/tmp/ python setup.py testr --coverage --testr-args='{posargs}' [flake8] # H803 skipped on purpose per list discussion. # E123, E125 skipped as they are invalid PEP-8. show-source = True -ignore = E123,E125,H803 +ignore = E123,E125,H803,W292 builtins = _ -exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
\ No newline at end of file +exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build |