From 204aba376de3ff283a7462d86eb1f5e92b0dccb2 Mon Sep 17 00:00:00 2001 From: Konsta Vesterinen Date: Wed, 17 Jun 2015 16:45:51 +0300 Subject: [PATCH] Fix babel primitive types - Make babel dependent primitive types to use Locale('en') for data validation instead of current locale. Using current locale leads to infinite recursion in cases where the loaded data has dependency to the loaded object's locale. --- CHANGES.rst | 1 + sqlalchemy_utils/__init__.py | 2 +- sqlalchemy_utils/i18n.py | 19 ++++++++----------- sqlalchemy_utils/primitives/country.py | 2 +- sqlalchemy_utils/primitives/currency.py | 14 ++++++-------- sqlalchemy_utils/primitives/weekday.py | 2 +- sqlalchemy_utils/types/currency.py | 9 ++------- sqlalchemy_utils/types/weekdays.py | 8 ++------ tests/__init__.py | 2 +- tests/primitives/test_country.py | 5 ++--- tests/primitives/test_currency.py | 5 ++--- tests/primitives/test_weekdays.py | 16 +++++----------- tests/test_translation_hybrid.py | 3 ++- tests/types/test_composite.py | 10 ++++------ tests/types/test_country.py | 9 +++++---- tests/types/test_currency.py | 5 ++--- tests/types/test_weekdays.py | 5 ++--- 17 files changed, 47 insertions(+), 70 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 6575b2c..b9a70f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -8,6 +8,7 @@ Here you can see the full list of changes between each SQLAlchemy-Utils release. ^^^^^^^^^^^^^^^^^^^^ - Added better support for dynamic locales in translation_hybrid +- Make babel dependent primitive types to use Locale('en') for data validation instead of current locale. Using current locale leads to infinite recursion in cases where the loaded data has dependency to the loaded object's locale. 0.30.9 (2015-06-09) diff --git a/sqlalchemy_utils/__init__.py b/sqlalchemy_utils/__init__.py index aaa425a..47b3ff7 100644 --- a/sqlalchemy_utils/__init__.py +++ b/sqlalchemy_utils/__init__.py @@ -92,4 +92,4 @@ from .types import ( # noqa WeekDaysType ) -__version__ = '0.30.9' +__version__ = '0.30.10' diff --git a/sqlalchemy_utils/i18n.py b/sqlalchemy_utils/i18n.py index 90135c6..b36e47f 100644 --- a/sqlalchemy_utils/i18n.py +++ b/sqlalchemy_utils/i18n.py @@ -3,18 +3,10 @@ from sqlalchemy.ext.hybrid import hybrid_property from .exceptions import ImproperlyConfigured -from babel import Locale - - try: - from babel.dates import get_day_names + import babel except ImportError: - def get_day_names(): - raise ImproperlyConfigured( - 'Could not load get_day_names function from babel. Either install ' - ' babel or make a similar function and override it in this ' - 'module.' - ) + babel = None try: from flask.ext.babel import get_locale @@ -29,6 +21,10 @@ except ImportError: class TranslationHybrid(object): def __init__(self, current_locale, default_locale, default_value=None): + if babel is None: + raise ImproperlyConfigured( + 'You need to install babel in order to use TranslationHybrid.' + ) self.current_locale = current_locale self.default_locale = default_locale self.default_value = default_value @@ -43,8 +39,9 @@ class TranslationHybrid(object): locale = locale() except TypeError: locale = locale(obj) - if isinstance(locale, Locale): + if isinstance(locale, babel.Locale): return str(locale) + return locale def getter_factory(self, attr): diff --git a/sqlalchemy_utils/primitives/country.py b/sqlalchemy_utils/primitives/country.py index bae9ea5..01e6261 100644 --- a/sqlalchemy_utils/primitives/country.py +++ b/sqlalchemy_utils/primitives/country.py @@ -71,7 +71,7 @@ class Country(object): @classmethod def validate(self, code): try: - i18n.get_locale().territories[code] + i18n.babel.Locale('en').territories[code] except KeyError: raise ValueError( 'Could not convert string to country code: {0}'.format(code) diff --git a/sqlalchemy_utils/primitives/currency.py b/sqlalchemy_utils/primitives/currency.py index d9a04c8..d27f688 100644 --- a/sqlalchemy_utils/primitives/currency.py +++ b/sqlalchemy_utils/primitives/currency.py @@ -1,9 +1,4 @@ # -*- coding: utf-8 -*- -babel = None -try: - import babel -except ImportError: - pass import six from sqlalchemy_utils import i18n, ImproperlyConfigured @@ -58,7 +53,7 @@ class Currency(object): """ def __init__(self, code): - if babel is None: + if i18n.babel is None: raise ImproperlyConfigured( "'babel' package is required in order to use Currency class." ) @@ -77,13 +72,16 @@ class Currency(object): @classmethod def validate(self, code): try: - i18n.get_locale().currencies[code] + i18n.babel.Locale('en').currencies[code] except KeyError: raise ValueError("{0}' is not valid currency code.") @property def symbol(self): - return babel.numbers.get_currency_symbol(self.code, i18n.get_locale()) + return i18n.babel.numbers.get_currency_symbol( + self.code, + i18n.get_locale() + ) @property def name(self): diff --git a/sqlalchemy_utils/primitives/weekday.py b/sqlalchemy_utils/primitives/weekday.py index c57d1ee..29a4443 100644 --- a/sqlalchemy_utils/primitives/weekday.py +++ b/sqlalchemy_utils/primitives/weekday.py @@ -39,7 +39,7 @@ class WeekDay(object): return self.name def get_name(self, width='wide', context='format'): - names = i18n.get_day_names( + names = i18n.babel.dates.get_day_names( width, context, i18n.get_locale() diff --git a/sqlalchemy_utils/types/currency.py b/sqlalchemy_utils/types/currency.py index f3290fe..6c8abbe 100644 --- a/sqlalchemy_utils/types/currency.py +++ b/sqlalchemy_utils/types/currency.py @@ -1,12 +1,7 @@ -babel = None -try: - import babel -except ImportError: - pass import six from sqlalchemy import types -from sqlalchemy_utils import ImproperlyConfigured +from sqlalchemy_utils import i18n, ImproperlyConfigured from sqlalchemy_utils.primitives import Currency from .scalar_coercible import ScalarCoercible @@ -57,7 +52,7 @@ class CurrencyType(types.TypeDecorator, ScalarCoercible): python_type = Currency def __init__(self, *args, **kwargs): - if babel is None: + if i18n.babel is None: raise ImproperlyConfigured( "'babel' package is required in order to use CurrencyType." ) diff --git a/sqlalchemy_utils/types/weekdays.py b/sqlalchemy_utils/types/weekdays.py index efd6232..e3c3e95 100644 --- a/sqlalchemy_utils/types/weekdays.py +++ b/sqlalchemy_utils/types/weekdays.py @@ -1,11 +1,7 @@ -babel = None -try: - import babel -except ImportError: - pass import six from sqlalchemy import types +from sqlalchemy_utils import i18n from sqlalchemy_utils.exceptions import ImproperlyConfigured from sqlalchemy_utils.primitives import WeekDay, WeekDays @@ -57,7 +53,7 @@ class WeekDaysType(types.TypeDecorator, ScalarCoercible): impl = BitType(WeekDay.NUM_WEEK_DAYS) def __init__(self, *args, **kwargs): - if babel is None: + if i18n.babel is None: raise ImproperlyConfigured( "'babel' package is required to use 'WeekDaysType'" ) diff --git a/tests/__init__.py b/tests/__init__.py index a2e3b97..7b32bd5 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -31,7 +31,7 @@ sa.event.listen(sa.orm.mapper, 'mapper_configured', coercion_listener) def get_locale(): class Locale(): - territories = {'fi': 'Finland'} + territories = {'FI': 'Finland'} return Locale() diff --git a/tests/primitives/test_country.py b/tests/primitives/test_country.py index 95463bc..751e876 100644 --- a/tests/primitives/test_country.py +++ b/tests/primitives/test_country.py @@ -2,13 +2,12 @@ import six from pytest import mark, raises from sqlalchemy_utils import Country, i18n -from sqlalchemy_utils.primitives.currency import babel # noqa -@mark.skipif('babel is None') +@mark.skipif('i18n.babel is None') class TestCountry(object): def setup_method(self, method): - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def test_init(self): assert Country(u'FI') == Country(Country(u'FI')) diff --git a/tests/primitives/test_currency.py b/tests/primitives/test_currency.py index bec1498..a6c4876 100644 --- a/tests/primitives/test_currency.py +++ b/tests/primitives/test_currency.py @@ -3,13 +3,12 @@ import six from pytest import mark, raises from sqlalchemy_utils import Currency, i18n -from sqlalchemy_utils.primitives.currency import babel # noqa -@mark.skipif('babel is None') +@mark.skipif('i18n.babel is None') class TestCurrency(object): def setup_method(self, method): - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def test_init(self): assert Currency('USD') == Currency(Currency('USD')) diff --git a/tests/primitives/test_weekdays.py b/tests/primitives/test_weekdays.py index 5e3f923..3e1b246 100644 --- a/tests/primitives/test_weekdays.py +++ b/tests/primitives/test_weekdays.py @@ -5,17 +5,11 @@ from flexmock import flexmock from sqlalchemy_utils import i18n from sqlalchemy_utils.primitives import WeekDay, WeekDays -Locale = None -try: - from babel import Locale -except ImportError: - pass - -@pytest.mark.skipif('Locale is None') +@pytest.mark.skipif('i18n.babel is None') class TestWeekDay(object): def setup_method(self, method): - i18n.get_locale = lambda: Locale('fi') + i18n.get_locale = lambda: i18n.babel.Locale('fi') def test_constructor_with_valid_index(self): day = WeekDay(1) @@ -88,7 +82,7 @@ class TestWeekDay(object): assert str(day) == 'maanantaina' -@pytest.mark.skipif('Locale is None') +@pytest.mark.skipif('i18n.babel is None') class TestWeekDays(object): def test_constructor_with_valid_bit_string(self): days = WeekDays('1000100') @@ -161,11 +155,11 @@ class TestWeekDays(object): assert indices == [1, 2, 3, 4, 5, 6, 0] def test_unicode(self): - i18n.get_locale = lambda: Locale('fi') + i18n.get_locale = lambda: i18n.babel.Locale('fi') days = WeekDays('1000100') assert six.text_type(days) == u'maanantaina, perjantaina' def test_str(self): - i18n.get_locale = lambda: Locale('fi') + i18n.get_locale = lambda: i18n.babel.Locale('fi') days = WeekDays('1000100') assert str(days) == 'maanantaina, perjantaina' diff --git a/tests/test_translation_hybrid.py b/tests/test_translation_hybrid.py index c83d79d..91b6572 100644 --- a/tests/test_translation_hybrid.py +++ b/tests/test_translation_hybrid.py @@ -2,10 +2,11 @@ import sqlalchemy as sa from pytest import mark from sqlalchemy.dialects.postgresql import HSTORE -from sqlalchemy_utils import TranslationHybrid +from sqlalchemy_utils import i18n, TranslationHybrid # noqa from tests import TestCase +@mark.skipif('i18n.babel is None') class TestTranslationHybrid(TestCase): dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' diff --git a/tests/types/test_composite.py b/tests/types/test_composite.py index 9f67272..d748606 100644 --- a/tests/types/test_composite.py +++ b/tests/types/test_composite.py @@ -15,7 +15,6 @@ from sqlalchemy_utils import ( remove_composite_listeners ) from sqlalchemy_utils.types import pg_composite -from sqlalchemy_utils.types.currency import babel from sqlalchemy_utils.types.range import intervals from tests import TestCase @@ -52,13 +51,13 @@ class TestCompositeTypeWithRegularTypes(TestCase): assert account.balance.amount == 15 -@mark.skipif('babel is None') +@mark.skipif('i18n.babel is None') class TestCompositeTypeWithTypeDecorators(TestCase): dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' def setup_method(self, method): TestCase.setup_method(self, method) - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def create_models(self): class Account(self.Base): @@ -101,7 +100,7 @@ class TestCompositeTypeWithTypeDecorators(TestCase): assert account.balance.amount == 15 -@mark.skipif('babel is None') +@mark.skipif('i18n.babel is None') class TestCompositeTypeInsideArray(TestCase): dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' @@ -115,7 +114,7 @@ class TestCompositeTypeInsideArray(TestCase): ) TestCase.setup_method(self, method) - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def create_models(self): class Account(self.Base): @@ -159,7 +158,6 @@ class TestCompositeTypeWithRangeTypeInsideArray(TestCase): ) TestCase.setup_method(self, method) - i18n.get_locale = lambda: babel.Locale('en') def create_models(self): class Account(self.Base): diff --git a/tests/types/test_country.py b/tests/types/test_country.py index 4003f2c..59bae9d 100644 --- a/tests/types/test_country.py +++ b/tests/types/test_country.py @@ -1,9 +1,11 @@ import sqlalchemy as sa +from pytest import mark -from sqlalchemy_utils import Country, CountryType +from sqlalchemy_utils import Country, CountryType, i18n # noqa from tests import TestCase +@mark.skipif('i18n.babel is None') class TestCountryType(TestCase): def create_models(self): class User(self.Base): @@ -18,7 +20,7 @@ class TestCountryType(TestCase): def test_parameter_processing(self): user = self.User( - country=Country(u'fi') + country=Country(u'FI') ) self.session.add(user) @@ -28,6 +30,5 @@ class TestCountryType(TestCase): assert user.country.name == u'Finland' def test_scalar_attributes_get_coerced_to_objects(self): - user = self.User(country='fi') - + user = self.User(country='FI') assert isinstance(user.country, Country) diff --git a/tests/types/test_currency.py b/tests/types/test_currency.py index 7a346be..caa7a1d 100644 --- a/tests/types/test_currency.py +++ b/tests/types/test_currency.py @@ -3,15 +3,14 @@ import sqlalchemy as sa from pytest import mark from sqlalchemy_utils import Currency, CurrencyType, i18n -from sqlalchemy_utils.types.currency import babel from tests import TestCase -@mark.skipif('babel is None') +@mark.skipif('i18n.babel is None') class TestCurrencyType(TestCase): def setup_method(self, method): TestCase.setup_method(self, method) - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def create_models(self): class User(self.Base): diff --git a/tests/types/test_weekdays.py b/tests/types/test_weekdays.py index d9c03d0..1bda6f2 100644 --- a/tests/types/test_weekdays.py +++ b/tests/types/test_weekdays.py @@ -4,15 +4,14 @@ import sqlalchemy as sa from sqlalchemy_utils import i18n from sqlalchemy_utils.primitives import WeekDays from sqlalchemy_utils.types import WeekDaysType -from sqlalchemy_utils.types.weekdays import babel from tests import TestCase -@pytest.mark.skipif('babel is None') +@pytest.mark.skipif('i18n.babel is None') class WeekDaysTypeTestCase(TestCase): def setup_method(self, method): TestCase.setup_method(self, method) - i18n.get_locale = lambda: babel.Locale('en') + i18n.get_locale = lambda: i18n.babel.Locale('en') def create_models(self): class Schedule(self.Base):