kopia lustrzana https://github.com/collective/icalendar
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
rodzic
f7f0c205bb
commit
e72a3a6d83
|
@ -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"]
|
||||
|
|
1
setup.py
1
setup.py
|
@ -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"',
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
21
tox.ini
|
@ -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
|
||||
|
|
Ładowanie…
Reference in New Issue