Skip to content

ValueError from failed import of localtime submodule #1092

@matthiashuschle

Description

@matthiashuschle

Overview Description

When calling routines like babel.number.get_decimal_symbol, an internal import of babel.localtime fails under certain circumstances with a ValueError, that may easily slip awareness.

Steps to Reproduce

  1. Have a Unix system
  2. set TZ=/UTC
  3. in Python 3.9+ without pytz, run babel.numbers.parse_decimal("5.2")

Actual Results

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/lib/python3.10/dist-packages/babel/numbers.py", line 1103, in parse_decimal
    group_symbol = get_group_symbol(locale, numbering_system=numbering_system)
  File "/usr/local/lib/python3.10/dist-packages/babel/numbers.py", line 452, in get_group_symbol
    return _get_number_symbols(locale, numbering_system=numbering_system).get('group', ',')
  File "/usr/local/lib/python3.10/dist-packages/babel/numbers.py", line 335, in _get_number_symbols
    return parsed_locale.number_symbols[numbering_system]
  File "/usr/local/lib/python3.10/dist-packages/babel/core.py", line 640, in number_symbols
    return self._data['number_symbols']
  File "/usr/local/lib/python3.10/dist-packages/babel/core.py", line 439, in _data
    self.__data = localedata.LocaleDataDict(localedata.load(str(self)))
  File "/usr/local/lib/python3.10/dist-packages/babel/localedata.py", line 137, in load
    data = load(parent).copy()
  File "/usr/local/lib/python3.10/dist-packages/babel/localedata.py", line 137, in load
    data = load(parent).copy()
  File "/usr/local/lib/python3.10/dist-packages/babel/localedata.py", line 137, in load
    data = load(parent).copy()
  File "/usr/local/lib/python3.10/dist-packages/babel/localedata.py", line 143, in load
    data = pickle.load(fileobj)
  File "/usr/local/lib/python3.10/dist-packages/babel/dates.py", line 34, in <module>
    from babel import localtime
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/__init__.py", line 41, in <module>
    LOCALTZ = get_localzone()
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/__init__.py", line 37, in get_localzone
    return _get_localzone()
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_unix.py", line 36, in _get_localzone
    return _tz_from_env(tzenv)
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_unix.py", line 21, in _tz_from_env
    return _get_tzinfo_or_raise(tzenv)
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_helpers.py", line 29, in _get_tzinfo_or_raise
    tzinfo = _get_tzinfo(tzenv)
  File "/usr/local/lib/python3.10/dist-packages/babel/localtime/_helpers.py", line 21, in _get_tzinfo
    return zoneinfo.ZoneInfo(tzenv)
  File "/usr/lib/python3.10/zoneinfo/_tzpath.py", line 67, in find_tzfile
    _validate_tzfile_path(key)
  File "/usr/lib/python3.10/zoneinfo/_tzpath.py", line 81, in _validate_tzfile_path
    raise ValueError(
ValueError: ZoneInfo keys may not be absolute paths, got: /UTC

Expected Results

Decimal('5.2')

Reproducibility

Works repeatedly with many calls, as long as they rely on localtime.

Additional Information

AFAICT the babel.localtime submodule is loaded lazily in the course of processing calls like babel.number.parse_decimal or babel.number.get_decimal_symbol. On systems where pytz is not present, zoneinfo is loaded in babel.localtime._unix.py via babel.localtime.__init__.py, where also get_localzone is called. On systems with TZ set (and it not being a local file), this triggers a call to zoneinfo.ZoneInfo with the value of TZ. This may be an invalid value, which raises an uncaught ValueError. This error is raised again on repeated calls, as the import fails.

While the workaround is pretty easy (install pytz), the problem here is awareness:

  • I don't think many people check the full env list for potential pitfalls. It may even be out of the user's control - especially on cloud services.
  • I can't think of a test that catches this accidentally.
  • Many of these calls are specifically wrapped to catch ValueError under the assumption that this means that the parsing failed due to the input value.

So this is a potential case of "fails only in production silently".

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions