diff --git a/babel/core.py b/babel/core.py index 8764360ab..cbff29f92 100644 --- a/babel/core.py +++ b/babel/core.py @@ -96,17 +96,29 @@ class UnknownLocaleError(Exception): is available. """ - def __init__(self, identifier): + def __init__(self, identifier, message=None): """Create the exception. :param identifier: the identifier string of the unsupported locale + :param message: an optional message string to override the default """ - Exception.__init__(self, 'unknown locale %r' % identifier) + if not message: + message = 'unknown locale %r' % identifier + Exception.__init__(self, message) #: The identifier of the locale that could not be found. self.identifier = identifier +class InvalidLocaleSpecificationError(ValueError): + def __init__(self, identifier, part_name, value): + ValueError.__init__( + self, + "The %s (%r) of the locale identifier %r is invalid." % (part_name, value, identifier) + ) + self.identifier = identifier + + class Locale(object): """Representation of a specific locale. @@ -156,16 +168,34 @@ def __init__(self, language, territory=None, script=None, variant=None): requested locale """ #: the language code - self.language = language + self.language = str(language) #: the territory (country or region) code - self.territory = territory + self.territory = (str(territory) if territory else None) #: the script code - self.script = script + self.script = (str(script) if script else None) #: the variant code - self.variant = variant + self.variant = (str(variant) if variant else None) self.__data = None + self._validate() + + def _validate(self): + """ + Validate that the locale parameters seem sane, and that the locale is known. + """ identifier = str(self) + if not self.language.isalpha(): + raise InvalidLocaleSpecificationError(identifier, "language", self.language) + + if self.territory and not (self.territory.isdigit() or (self.territory.isalpha() and self.territory.isupper())): + raise InvalidLocaleSpecificationError(identifier, "territory", self.territory) + + if self.script and not self.script.isalpha(): + raise InvalidLocaleSpecificationError(identifier, "script", self.script) + + if self.variant and not self.variant.isalpha(): + raise InvalidLocaleSpecificationError(identifier, "variant", self.variant) + if not localedata.exists(identifier): raise UnknownLocaleError(identifier) @@ -808,7 +838,7 @@ def interval_formats(self): How to format date intervals in Finnish when the day is the smallest changing component: - >>> Locale('fi_FI').interval_formats['MEd']['d'] + >>> Locale('fi', 'FI').interval_formats['MEd']['d'] [u'E d. \u2013 ', u'E d.M.'] .. seealso:: @@ -846,7 +876,7 @@ def list_patterns(self): u'{0}, {1}' >>> Locale('en').list_patterns['end'] u'{0}, and {1}' - >>> Locale('en_GB').list_patterns['end'] + >>> Locale('en', 'GB').list_patterns['end'] u'{0} and {1}' """ return self._data['list_patterns'] diff --git a/tests/conftest.py b/tests/conftest.py index 117a1307f..be93b2be7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,3 +7,9 @@ def os_environ(monkeypatch): mock_environ = dict(os.environ) monkeypatch.setattr(os, 'environ', mock_environ) return mock_environ + + +def pytest_generate_tests(metafunc): + if hasattr(metafunc.function, "all_locales"): + from babel.localedata import locale_identifiers + metafunc.parametrize("locale", list(locale_identifiers())) diff --git a/tests/messages/test_checkers.py b/tests/messages/test_checkers.py index b954acba7..65d138b08 100644 --- a/tests/messages/test_checkers.py +++ b/tests/messages/test_checkers.py @@ -95,7 +95,7 @@ def test_2_num_plurals_checkers(self): num_plurals = PLURALS[_locale][0] plural_expr = PLURALS[_locale][1] try: - locale = Locale(_locale) + locale = Locale.parse(_locale) date = format_datetime(datetime.now(LOCALTZ), 'yyyy-MM-dd HH:mmZ', tzinfo=LOCALTZ, locale=_locale) diff --git a/tests/test_core.py b/tests/test_core.py index 54cf37dde..635c1af8a 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -16,7 +16,7 @@ import pytest from babel import core, Locale -from babel.core import default_locale, Locale +from babel.core import default_locale, Locale, InvalidLocaleSpecificationError def test_locale_provides_access_to_cldr_locale_data(): @@ -35,14 +35,11 @@ def test_locale_comparison(): en_US = Locale('en', 'US') en_US_2 = Locale('en', 'US') fi_FI = Locale('fi', 'FI') - bad_en_US = Locale('en_US') assert en_US == en_US assert en_US == en_US_2 assert en_US != fi_FI assert not (en_US != en_US_2) assert None != en_US - assert en_US != bad_en_US - assert fi_FI != bad_en_US def test_can_return_default_locale(os_environ): @@ -315,3 +312,25 @@ def find_class(self, module, name): with open(filename, 'rb') as f: return Unpickler(f).load() + + +@pytest.mark.parametrize("parts", [ + ("en", "US", "Hans", "BAHAMAS"), + ("yi", "001"), +]) +def test_locale_ctor_validation(parts): + part_names = ("language", "territory", "script", "variant") + n = len(parts) + for i in range(n): + mangled_parts = list(parts) + for x in range(i, n): + mangled_parts[x] = "~~" + assert len(mangled_parts) == n + with pytest.raises(InvalidLocaleSpecificationError) as ei: + Locale(*mangled_parts) + assert part_names[i] in str(ei.value) # assert the complaint was about the first mangled part + + +@pytest.mark.all_locales +def test_locale_load(locale): + assert str(Locale.parse(locale)) == locale