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.
This commit is contained in:
Konsta Vesterinen
2015-06-17 16:45:51 +03:00
parent a6973658d3
commit 204aba376d
17 changed files with 47 additions and 70 deletions

View File

@@ -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 - 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) 0.30.9 (2015-06-09)

View File

@@ -92,4 +92,4 @@ from .types import ( # noqa
WeekDaysType WeekDaysType
) )
__version__ = '0.30.9' __version__ = '0.30.10'

View File

@@ -3,18 +3,10 @@ from sqlalchemy.ext.hybrid import hybrid_property
from .exceptions import ImproperlyConfigured from .exceptions import ImproperlyConfigured
from babel import Locale
try: try:
from babel.dates import get_day_names import babel
except ImportError: except ImportError:
def get_day_names(): babel = None
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.'
)
try: try:
from flask.ext.babel import get_locale from flask.ext.babel import get_locale
@@ -29,6 +21,10 @@ except ImportError:
class TranslationHybrid(object): class TranslationHybrid(object):
def __init__(self, current_locale, default_locale, default_value=None): 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.current_locale = current_locale
self.default_locale = default_locale self.default_locale = default_locale
self.default_value = default_value self.default_value = default_value
@@ -43,8 +39,9 @@ class TranslationHybrid(object):
locale = locale() locale = locale()
except TypeError: except TypeError:
locale = locale(obj) locale = locale(obj)
if isinstance(locale, Locale): if isinstance(locale, babel.Locale):
return str(locale) return str(locale)
return locale return locale
def getter_factory(self, attr): def getter_factory(self, attr):

View File

@@ -71,7 +71,7 @@ class Country(object):
@classmethod @classmethod
def validate(self, code): def validate(self, code):
try: try:
i18n.get_locale().territories[code] i18n.babel.Locale('en').territories[code]
except KeyError: except KeyError:
raise ValueError( raise ValueError(
'Could not convert string to country code: {0}'.format(code) 'Could not convert string to country code: {0}'.format(code)

View File

@@ -1,9 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
babel = None
try:
import babel
except ImportError:
pass
import six import six
from sqlalchemy_utils import i18n, ImproperlyConfigured from sqlalchemy_utils import i18n, ImproperlyConfigured
@@ -58,7 +53,7 @@ class Currency(object):
""" """
def __init__(self, code): def __init__(self, code):
if babel is None: if i18n.babel is None:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"'babel' package is required in order to use Currency class." "'babel' package is required in order to use Currency class."
) )
@@ -77,13 +72,16 @@ class Currency(object):
@classmethod @classmethod
def validate(self, code): def validate(self, code):
try: try:
i18n.get_locale().currencies[code] i18n.babel.Locale('en').currencies[code]
except KeyError: except KeyError:
raise ValueError("{0}' is not valid currency code.") raise ValueError("{0}' is not valid currency code.")
@property @property
def symbol(self): 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 @property
def name(self): def name(self):

View File

@@ -39,7 +39,7 @@ class WeekDay(object):
return self.name return self.name
def get_name(self, width='wide', context='format'): def get_name(self, width='wide', context='format'):
names = i18n.get_day_names( names = i18n.babel.dates.get_day_names(
width, width,
context, context,
i18n.get_locale() i18n.get_locale()

View File

@@ -1,12 +1,7 @@
babel = None
try:
import babel
except ImportError:
pass
import six import six
from sqlalchemy import types from sqlalchemy import types
from sqlalchemy_utils import ImproperlyConfigured from sqlalchemy_utils import i18n, ImproperlyConfigured
from sqlalchemy_utils.primitives import Currency from sqlalchemy_utils.primitives import Currency
from .scalar_coercible import ScalarCoercible from .scalar_coercible import ScalarCoercible
@@ -57,7 +52,7 @@ class CurrencyType(types.TypeDecorator, ScalarCoercible):
python_type = Currency python_type = Currency
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if babel is None: if i18n.babel is None:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"'babel' package is required in order to use CurrencyType." "'babel' package is required in order to use CurrencyType."
) )

View File

@@ -1,11 +1,7 @@
babel = None
try:
import babel
except ImportError:
pass
import six import six
from sqlalchemy import types from sqlalchemy import types
from sqlalchemy_utils import i18n
from sqlalchemy_utils.exceptions import ImproperlyConfigured from sqlalchemy_utils.exceptions import ImproperlyConfigured
from sqlalchemy_utils.primitives import WeekDay, WeekDays from sqlalchemy_utils.primitives import WeekDay, WeekDays
@@ -57,7 +53,7 @@ class WeekDaysType(types.TypeDecorator, ScalarCoercible):
impl = BitType(WeekDay.NUM_WEEK_DAYS) impl = BitType(WeekDay.NUM_WEEK_DAYS)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
if babel is None: if i18n.babel is None:
raise ImproperlyConfigured( raise ImproperlyConfigured(
"'babel' package is required to use 'WeekDaysType'" "'babel' package is required to use 'WeekDaysType'"
) )

View File

@@ -31,7 +31,7 @@ sa.event.listen(sa.orm.mapper, 'mapper_configured', coercion_listener)
def get_locale(): def get_locale():
class Locale(): class Locale():
territories = {'fi': 'Finland'} territories = {'FI': 'Finland'}
return Locale() return Locale()

View File

@@ -2,13 +2,12 @@ import six
from pytest import mark, raises from pytest import mark, raises
from sqlalchemy_utils import Country, i18n 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): class TestCountry(object):
def setup_method(self, method): def setup_method(self, method):
i18n.get_locale = lambda: babel.Locale('en') i18n.get_locale = lambda: i18n.babel.Locale('en')
def test_init(self): def test_init(self):
assert Country(u'FI') == Country(Country(u'FI')) assert Country(u'FI') == Country(Country(u'FI'))

View File

@@ -3,13 +3,12 @@ import six
from pytest import mark, raises from pytest import mark, raises
from sqlalchemy_utils import Currency, i18n 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): class TestCurrency(object):
def setup_method(self, method): def setup_method(self, method):
i18n.get_locale = lambda: babel.Locale('en') i18n.get_locale = lambda: i18n.babel.Locale('en')
def test_init(self): def test_init(self):
assert Currency('USD') == Currency(Currency('USD')) assert Currency('USD') == Currency(Currency('USD'))

View File

@@ -5,17 +5,11 @@ from flexmock import flexmock
from sqlalchemy_utils import i18n from sqlalchemy_utils import i18n
from sqlalchemy_utils.primitives import WeekDay, WeekDays from sqlalchemy_utils.primitives import WeekDay, WeekDays
Locale = None
try:
from babel import Locale
except ImportError:
pass
@pytest.mark.skipif('i18n.babel is None')
@pytest.mark.skipif('Locale is None')
class TestWeekDay(object): class TestWeekDay(object):
def setup_method(self, method): 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): def test_constructor_with_valid_index(self):
day = WeekDay(1) day = WeekDay(1)
@@ -88,7 +82,7 @@ class TestWeekDay(object):
assert str(day) == 'maanantaina' assert str(day) == 'maanantaina'
@pytest.mark.skipif('Locale is None') @pytest.mark.skipif('i18n.babel is None')
class TestWeekDays(object): class TestWeekDays(object):
def test_constructor_with_valid_bit_string(self): def test_constructor_with_valid_bit_string(self):
days = WeekDays('1000100') days = WeekDays('1000100')
@@ -161,11 +155,11 @@ class TestWeekDays(object):
assert indices == [1, 2, 3, 4, 5, 6, 0] assert indices == [1, 2, 3, 4, 5, 6, 0]
def test_unicode(self): def test_unicode(self):
i18n.get_locale = lambda: Locale('fi') i18n.get_locale = lambda: i18n.babel.Locale('fi')
days = WeekDays('1000100') days = WeekDays('1000100')
assert six.text_type(days) == u'maanantaina, perjantaina' assert six.text_type(days) == u'maanantaina, perjantaina'
def test_str(self): def test_str(self):
i18n.get_locale = lambda: Locale('fi') i18n.get_locale = lambda: i18n.babel.Locale('fi')
days = WeekDays('1000100') days = WeekDays('1000100')
assert str(days) == 'maanantaina, perjantaina' assert str(days) == 'maanantaina, perjantaina'

View File

@@ -2,10 +2,11 @@ import sqlalchemy as sa
from pytest import mark from pytest import mark
from sqlalchemy.dialects.postgresql import HSTORE from sqlalchemy.dialects.postgresql import HSTORE
from sqlalchemy_utils import TranslationHybrid from sqlalchemy_utils import i18n, TranslationHybrid # noqa
from tests import TestCase from tests import TestCase
@mark.skipif('i18n.babel is None')
class TestTranslationHybrid(TestCase): class TestTranslationHybrid(TestCase):
dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' dns = 'postgres://postgres@localhost/sqlalchemy_utils_test'

View File

@@ -15,7 +15,6 @@ from sqlalchemy_utils import (
remove_composite_listeners remove_composite_listeners
) )
from sqlalchemy_utils.types import pg_composite from sqlalchemy_utils.types import pg_composite
from sqlalchemy_utils.types.currency import babel
from sqlalchemy_utils.types.range import intervals from sqlalchemy_utils.types.range import intervals
from tests import TestCase from tests import TestCase
@@ -52,13 +51,13 @@ class TestCompositeTypeWithRegularTypes(TestCase):
assert account.balance.amount == 15 assert account.balance.amount == 15
@mark.skipif('babel is None') @mark.skipif('i18n.babel is None')
class TestCompositeTypeWithTypeDecorators(TestCase): class TestCompositeTypeWithTypeDecorators(TestCase):
dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' dns = 'postgres://postgres@localhost/sqlalchemy_utils_test'
def setup_method(self, method): def setup_method(self, method):
TestCase.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): def create_models(self):
class Account(self.Base): class Account(self.Base):
@@ -101,7 +100,7 @@ class TestCompositeTypeWithTypeDecorators(TestCase):
assert account.balance.amount == 15 assert account.balance.amount == 15
@mark.skipif('babel is None') @mark.skipif('i18n.babel is None')
class TestCompositeTypeInsideArray(TestCase): class TestCompositeTypeInsideArray(TestCase):
dns = 'postgres://postgres@localhost/sqlalchemy_utils_test' dns = 'postgres://postgres@localhost/sqlalchemy_utils_test'
@@ -115,7 +114,7 @@ class TestCompositeTypeInsideArray(TestCase):
) )
TestCase.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): def create_models(self):
class Account(self.Base): class Account(self.Base):
@@ -159,7 +158,6 @@ class TestCompositeTypeWithRangeTypeInsideArray(TestCase):
) )
TestCase.setup_method(self, method) TestCase.setup_method(self, method)
i18n.get_locale = lambda: babel.Locale('en')
def create_models(self): def create_models(self):
class Account(self.Base): class Account(self.Base):

View File

@@ -1,9 +1,11 @@
import sqlalchemy as sa 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 from tests import TestCase
@mark.skipif('i18n.babel is None')
class TestCountryType(TestCase): class TestCountryType(TestCase):
def create_models(self): def create_models(self):
class User(self.Base): class User(self.Base):
@@ -18,7 +20,7 @@ class TestCountryType(TestCase):
def test_parameter_processing(self): def test_parameter_processing(self):
user = self.User( user = self.User(
country=Country(u'fi') country=Country(u'FI')
) )
self.session.add(user) self.session.add(user)
@@ -28,6 +30,5 @@ class TestCountryType(TestCase):
assert user.country.name == u'Finland' assert user.country.name == u'Finland'
def test_scalar_attributes_get_coerced_to_objects(self): def test_scalar_attributes_get_coerced_to_objects(self):
user = self.User(country='fi') user = self.User(country='FI')
assert isinstance(user.country, Country) assert isinstance(user.country, Country)

View File

@@ -3,15 +3,14 @@ import sqlalchemy as sa
from pytest import mark from pytest import mark
from sqlalchemy_utils import Currency, CurrencyType, i18n from sqlalchemy_utils import Currency, CurrencyType, i18n
from sqlalchemy_utils.types.currency import babel
from tests import TestCase from tests import TestCase
@mark.skipif('babel is None') @mark.skipif('i18n.babel is None')
class TestCurrencyType(TestCase): class TestCurrencyType(TestCase):
def setup_method(self, method): def setup_method(self, method):
TestCase.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): def create_models(self):
class User(self.Base): class User(self.Base):

View File

@@ -4,15 +4,14 @@ import sqlalchemy as sa
from sqlalchemy_utils import i18n from sqlalchemy_utils import i18n
from sqlalchemy_utils.primitives import WeekDays from sqlalchemy_utils.primitives import WeekDays
from sqlalchemy_utils.types import WeekDaysType from sqlalchemy_utils.types import WeekDaysType
from sqlalchemy_utils.types.weekdays import babel
from tests import TestCase from tests import TestCase
@pytest.mark.skipif('babel is None') @pytest.mark.skipif('i18n.babel is None')
class WeekDaysTypeTestCase(TestCase): class WeekDaysTypeTestCase(TestCase):
def setup_method(self, method): def setup_method(self, method):
TestCase.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): def create_models(self):
class Schedule(self.Base): class Schedule(self.Base):