Remove pytz from dependencies

Until now, icalendar was using pytz.
This removes the dependency pytz.
Tests run with pytz and without.
The CI also runs the tests without pytz.
The default implementation of icalendar uses zoneinfo.
pull/650/head
Nicco Kunzmann 2024-06-24 18:16:49 +01:00
rodzic f7f0c205bb
commit e72a3a6d83
9 zmienionych plików z 96 dodań i 28 usunięć

Wyświetl plik

@ -19,6 +19,7 @@ jobs:
# [Python version, tox env]
- ["3.7", "py37"]
- ["3.8", "py38"]
- ["3.8", "nopytz"]
- ["3.9", "py39"]
- ["3.10", "py310"]
- ["pypy-3.9", "pypy3"]

Wyświetl plik

@ -20,7 +20,6 @@ for fname in ['README.rst', 'CONTRIBUTING.rst', 'CHANGES.rst', 'LICENSE.rst']:
tests_require = []
install_requires = [
'python-dateutil',
'pytz',
# install requirements depending on python version
# see https://www.python.org/dev/peps/pep-0508/#environment-markers
'backports.zoneinfo; python_version == "3.7" or python_version == "3.8"',

Wyświetl plik

@ -4,7 +4,10 @@ except ImportError:
import zoneinfo
import pytest
import icalendar
import pytz
try:
import pytz
except ImportError:
pytz = None
from datetime import datetime
from dateutil import tz
from icalendar.cal import Component, Calendar, Event, ComponentFactory
@ -14,6 +17,21 @@ from pathlib import Path
import itertools
import sys
HAS_PYTZ = pytz is not None
if HAS_PYTZ:
PYTZ_UTC = [
pytz.utc,
pytz.timezone('UTC'),
]
PYTZ_IN_TIMEZONE = [
lambda dt, tzname: pytz.timezone(tzname).localize(dt),
]
PYTZ_TZP = ["pytz"]
else:
PYTZ_UTC = []
PYTZ_IN_TIMEZONE = []
PYTZ_TZP = []
class DataSource:
'''A collection of parsed ICS elements (e.g calendars, timezones, events)'''
@ -77,17 +95,14 @@ def timezones(tzp):
def events(tzp):
return DataSource(EVENTS_FOLDER, icalendar.Event.from_ical)
@pytest.fixture(params=[
pytz.utc,
@pytest.fixture(params=PYTZ_UTC + [
zoneinfo.ZoneInfo('UTC'),
pytz.timezone('UTC'),
tz.UTC,
tz.gettz('UTC')])
def utc(request, tzp):
return request.param
@pytest.fixture(params=[
lambda dt, tzname: pytz.timezone(tzname).localize(dt),
@pytest.fixture(params=PYTZ_IN_TIMEZONE + [
lambda dt, tzname: dt.replace(tzinfo=tz.gettz(tzname)),
lambda dt, tzname: dt.replace(tzinfo=zoneinfo.ZoneInfo(tzname))
])
@ -194,7 +209,7 @@ def tzp(tzp_name):
_tzp.use_default()
@pytest.fixture(params=["pytz", "zoneinfo"])
@pytest.fixture(params=PYTZ_TZP + ["zoneinfo"])
def other_tzp(request, tzp):
"""This is annother timezone provider.
@ -229,12 +244,12 @@ def pytest_generate_tests(metafunc):
See https://docs.pytest.org/en/6.2.x/example/parametrize.html#deferring-the-setup-of-parametrized-resources
"""
if "tzp_name" in metafunc.fixturenames:
tzp_names = ["pytz", "zoneinfo"]
tzp_names = PYTZ_TZP + ["zoneinfo"]
if "zoneinfo_only" in metafunc.fixturenames:
tzp_names.remove("pytz")
if "pytz_only" in metafunc.fixturenames:
tzp_names = ["zoneinfo"]
if "pytz_only" in metafunc.fixturenames:
tzp_names.remove("zoneinfo")
assert tzp_names, "Use pytz_only or zoneinfo_only but not both!"
assert not ("zoneinfo_only" in metafunc.fixturenames and "pytz_only" in metafunc.fixturenames), "Use pytz_only or zoneinfo_only but not both!"
metafunc.parametrize("tzp_name", tzp_names, scope="module")
@ -244,13 +259,19 @@ class DoctestZoneInfo(zoneinfo.ZoneInfo):
return f"ZoneInfo(key={repr(self.key)})"
def test_print(obj):
def doctest_print(obj):
"""doctest print"""
if isinstance(obj, bytes):
obj = obj.decode("UTF-8")
print(str(obj).strip().replace("\r\n", "\n").replace("\r", "\n"))
def doctest_import(name, *args, **kw):
"""Replace the import mechanism to skip the whole doctest if we import pytz."""
if name == "pytz":
return pytz
return __import__(name, *args, **kw)
@pytest.fixture()
def env_for_doctest(monkeypatch):
"""Modify the environment to make doctests run."""
@ -258,4 +279,7 @@ def env_for_doctest(monkeypatch):
monkeypatch.setattr(zoneinfo, "ZoneInfo", DoctestZoneInfo)
from icalendar.timezone.zoneinfo import ZONEINFO
monkeypatch.setattr(ZONEINFO, "utc", zoneinfo.ZoneInfo("UTC"))
return {"print": test_print}
return {
"print": doctest_print,
"__import__": doctest_import,
}

Wyświetl plik

@ -2,7 +2,10 @@
from icalendar import vDDDTypes
from datetime import datetime, date, time
from icalendar.timezone.zoneinfo import zoneinfo
import pytz
try:
import pytz
except ImportError:
pytz = None
from dateutil import tz
import pytest
from copy import deepcopy
@ -10,7 +13,6 @@ from copy import deepcopy
vDDDTypes_list = [
vDDDTypes(pytz.timezone('EST').localize(datetime(year=2022, month=7, day=22, hour=12, minute=7))),
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=zoneinfo.ZoneInfo("Europe/London"))),
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7)),
vDDDTypes(datetime(year=2022, month=7, day=22, hour=12, minute=7, tzinfo=tz.UTC)),
@ -18,6 +20,8 @@ vDDDTypes_list = [
vDDDTypes(date(year=2022, month=7, day=23)),
vDDDTypes(time(hour=22, minute=7, second=2))
]
if pytz:
vDDDTypes_list.append(vDDDTypes(pytz.timezone('EST').localize(datetime(year=2022, month=7, day=22, hour=12, minute=7))),)
def identity(x):
return x

Wyświetl plik

@ -1,6 +1,9 @@
"""Test the equality and inequality of components."""
import copy
import pytz
try:
import pytz
except ImportError:
pytz = None
from icalendar.prop import *
from datetime import datetime, date, timedelta
import pytest

Wyświetl plik

@ -2,19 +2,31 @@
These are mostly located in icalendar.timezone.
"""
import pytz
try:
import pytz
from icalendar.timezone.pytz import PYTZ
except ImportError:
pytz = None
from icalendar.timezone.zoneinfo import zoneinfo, ZONEINFO
from dateutil.tz.tz import _tzicalvtz
from icalendar.timezone.pytz import PYTZ
import pytest
import copy
import pickle
from dateutil.rrule import rrule, MONTHLY
from datetime import datetime
if pytz:
PYTZ_TIMEZONES = pytz.all_timezones
TZP_ = [PYTZ(), ZONEINFO()]
NEW_TZP_NAME = ["pytz", "zoneinfo"]
else:
PYTZ_TIMEZONES = []
TZP_ = [ZONEINFO()]
NEW_TZP_NAME = ["zoneinfo"]
@pytest.mark.parametrize("tz_name", pytz.all_timezones + list(zoneinfo.available_timezones()))
@pytest.mark.parametrize("tzp_", [PYTZ(), ZONEINFO()])
@pytest.mark.parametrize("tz_name", PYTZ_TIMEZONES + list(zoneinfo.available_timezones()))
@pytest.mark.parametrize("tzp_", TZP_)
def test_timezone_names_are_known(tz_name, tzp_):
"""Make sure that all timezones are understood."""
if tz_name in ("Factory", "localtime"):
@ -55,7 +67,7 @@ def test_cache_reuse_timezone_cache(tzp, timezones):
assert tzp1 is tzp.timezone("custom_Pacific/Fiji"), "Cache is not replaced."
@pytest.mark.parametrize("new_tzp_name", ["pytz", "zoneinfo"])
@pytest.mark.parametrize("new_tzp_name", NEW_TZP_NAME)
def test_cache_is_emptied_when_tzp_is_switched(tzp, timezones, new_tzp_name):
"""Make sure we do not reuse the timezones created when we switch the provider."""
tzp.cache_timezone_component(timezones.pacific_fiji)

Wyświetl plik

@ -14,6 +14,7 @@ import doctest
import os
import pytest
import importlib
import sys
HERE = os.path.dirname(__file__) or "."
ICALENDAR_PATH = os.path.dirname(HERE)
@ -35,7 +36,12 @@ def test_this_module_is_among_them():
@pytest.mark.parametrize("module_name", MODULE_NAMES)
def test_docstring_of_python_file(module_name):
"""This test runs doctest on the Python module."""
module = importlib.import_module(module_name)
try:
module = importlib.import_module(module_name)
except ModuleNotFoundError as e:
if e.name == "pytz":
pytest.skip("pytz is not installed, skipping this module.")
raise
test_result = doctest.testmod(module, name=module_name)
assert test_result.failed == 0, f"{test_result.failed} errors in {module_name}"
@ -68,11 +74,11 @@ def test_documentation_file(document, zoneinfo_only, env_for_doctest):
functions are also replaced to work.
"""
test_result = doctest.testfile(document, module_relative=False, globs=env_for_doctest)
if env_for_doctest.get("pytz") is None:
pytest.skip("pytz was imported but could not be used.")
assert test_result.failed == 0, f"{test_result.failed} errors in {os.path.basename(document)}"
def test_can_import_zoneinfo(env_for_doctest):
"""Allow importing zoneinfo for tests."""
import pytz
import zoneinfo
from dateutil import tz
assert "zoneinfo" in sys.modules

Wyświetl plik

@ -8,7 +8,7 @@ from icalendar import prop
from dateutil.rrule import rrule
DEFAULT_TIMEZONE_PROVIDER = "pytz"
DEFAULT_TIMEZONE_PROVIDER = "zoneinfo"
class TZP:

21
tox.ini
Wyświetl plik

@ -1,6 +1,6 @@
# to run for a specific environment, use ``tox -e ENVNAME``
[tox]
envlist = py37,py38,py39,py310,py311,pypy3,docs
envlist = py37,py38,py39,py310,py311,pypy3,docs,nopytz
# Note: the 'docs' env creates a 'build' directory which may interfere in strange ways
# with the other environments. You might see this when you run the tests in parallel.
# See https://github.com/collective/icalendar/pull/359#issuecomment-1214150269
@ -11,11 +11,30 @@ deps =
pytest
coverage
hypothesis
pytz
commands =
coverage run --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest []
coverage report
coverage html
[testenv:nopytz]
# install with dependencies
usedevelop = False
# use lowest version
basepython = python3.8
allowlist_externals =
rm
deps =
setuptools>=70.1.0
pytest
coverage
hypothesis
commands =
rm -rf build # do not mess up import
coverage run --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest []
coverage report
coverage html
[testenv:docs]
deps =
-r {toxinidir}/requirements_docs.txt