Skip to content

Commit

Permalink
Validate Locale parameter format
Browse files Browse the repository at this point in the history
  • Loading branch information
akx committed Jan 23, 2016
1 parent 0d78912 commit 42b9906
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 13 deletions.
46 changes: 38 additions & 8 deletions babel/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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::
Expand Down Expand Up @@ -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']
Expand Down
2 changes: 1 addition & 1 deletion tests/messages/test_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 18 additions & 4 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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):
Expand Down Expand Up @@ -315,3 +312,20 @@ 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

0 comments on commit 42b9906

Please sign in to comment.