Skip to content

Commit

Permalink
Merge branch 'main' into pr-634
Browse files Browse the repository at this point in the history
  • Loading branch information
niccokunzmann committed Jun 25, 2024
2 parents 9dc564b + 1bc9784 commit 65c350e
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 40 deletions.
28 changes: 23 additions & 5 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
config:
# [Python version, tox env]
- ["3.8", "py38"]
- ["3.8", "nopytz"]
- ["3.9", "py39"]
- ["3.10", "py310"]
- ["pypy-3.9", "pypy3"]
Expand All @@ -43,15 +44,32 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
pip install tox coveralls coverage-python-version
- name: Test
run: tox -e ${{ matrix.config[1] }}
- name: Coverage
run: |
pip install coveralls coverage-python-version
coveralls --service=github
- name: Upload coverage data to coveralls.io
run: coveralls --service=github-actions
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.CODECOV_TOKEN }}
COVERALLS_FLAG_NAME: ${{ matrix.config[1] }}
COVERALLS_PARALLEL: true

coverage:
# parallel test coverage upload
# see https://coveralls-python.readthedocs.io/en/latest/usage/configuration.html#github-actions-support
name: Submit test coverage
needs: run-tests
runs-on: ubuntu-latest
container: python:3-slim
steps:
- name: Install dependencies
run: pip3 install --upgrade coveralls
- name: Upload coverage
run: coveralls --service=github-actions --finish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
COVERALLS_REPO_TOKEN: ${{ secrets.CODECOV_TOKEN }}

deploy-tag-to-pypi:
# only deploy on tags, see https://stackoverflow.com/a/58478262/1320237
Expand Down
9 changes: 8 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@ Changelog
Minor changes:

- Test that all code works with both ``pytz`` and ``zoneinfo``.
- Make coverage report submission optional for pull requests
- Rename ``master`` branch to ``main``, see `Issue
<https://github.com/collective/icalendar/issues/627>`_

- Added missing public classes and functions to API documentation.
- Add version badge
- Update list of ``tox`` environments

Breaking changes:

Expand Down Expand Up @@ -49,9 +50,15 @@ Breaking changes:
- Remove Python 3.7 as compatible. icalendar is compatible with Python
versions 3.8 - 3.12, and PyPy3.

- Remove ``pytz`` as a dependency of ``icalendar``. If you require ``pytz``,
add it to your dependency list or install it additionally with::

pip install icalendar==6.* pytz

New features:

- Test compatibility with Python 3.12
- Add function ``icalendar.use_pytz()``.

Bug fixes:

Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
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.9"',
Expand Down
62 changes: 42 additions & 20 deletions src/icalendar/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
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
from icalendar.timezone import tzp as _tzp
Expand All @@ -13,6 +17,21 @@
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)'''
Expand Down Expand Up @@ -76,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))
])
Expand Down Expand Up @@ -193,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.
Expand All @@ -207,15 +223,13 @@ def other_tzp(request, tzp):
@pytest.fixture()
def pytz_only(tzp):
"""Skip tests that are not running under pytz."""
if not tzp.uses_pytz():
pytest.skip("Not using pytz. Skipping this test.")
assert tzp.uses_pytz()


@pytest.fixture()
def zoneinfo_only(tzp):
"""Skip tests that are not running under pytz."""
if not tzp.uses_zoneinfo():
pytest.skip("Not using zoneinfo. Skipping this test.")
def zoneinfo_only(tzp, request, tzp_name):
"""Skip tests that are not running under zoneinfo."""
assert tzp.uses_zoneinfo()


def pytest_generate_tests(metafunc):
Expand All @@ -228,12 +242,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.remove("zoneinfo")
assert tzp_names, "Use pytz_only or zoneinfo_only but not both!"
tzp_names = ["zoneinfo"]
if "pytz_only" in metafunc.fixturenames:
tzp_names = PYTZ_TZP
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")


Expand All @@ -243,18 +257,26 @@ def __repr__(self):
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."""
monkeypatch.setitem(sys.modules, "zoneinfo", zoneinfo)
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
}
8 changes: 6 additions & 2 deletions src/icalendar/tests/prop/test_identity_and_equality.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,26 @@
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



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)),
vDDDTypes(date(year=2022, month=7, day=22)),
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
Expand Down
5 changes: 4 additions & 1 deletion src/icalendar/tests/test_equality.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
22 changes: 17 additions & 5 deletions src/icalendar/tests/test_pytz_zoneinfo_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand Down Expand Up @@ -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)
Expand Down
21 changes: 18 additions & 3 deletions src/icalendar/tests/test_with_doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import os
import pytest
import importlib
import sys
import re

HERE = os.path.dirname(__file__) or "."
ICALENDAR_PATH = os.path.dirname(HERE)
Expand All @@ -35,7 +37,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}"

Expand All @@ -62,14 +69,22 @@ def test_files_is_included(filename):


@pytest.mark.parametrize("document", DOCUMENT_PATHS)
def test_documentation_file(document, zoneinfo_only, env_for_doctest):
def test_documentation_file(document, zoneinfo_only, env_for_doctest, tzp):
"""This test runs doctest on a documentation file.
functions are also replaced to work.
"""
test_result = doctest.testfile(document, module_relative=False, globs=env_for_doctest)
try:
test_result = doctest.testfile(document, module_relative=False, globs=env_for_doctest, raise_on_error=True)
except doctest.UnexpectedException as e:
ty, err, tb = e.exc_info
if issubclass(ty, ModuleNotFoundError) and err.name == "pytz":
pytest.skip("pytz not installed, skipping this file.")
finally:
tzp.use_zoneinfo()
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."""
assert "zoneinfo" in sys.modules
2 changes: 1 addition & 1 deletion src/icalendar/timezone/tzp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from dateutil.rrule import rrule


DEFAULT_TIMEZONE_PROVIDER = "pytz"
DEFAULT_TIMEZONE_PROVIDER = "zoneinfo"


class TZP:
Expand Down
21 changes: 20 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# to run for a specific environment, use ``tox -e ENVNAME``
[tox]
envlist = py37,py38,py39,py310,py311,pypy3,docs
envlist = py38,py39,py310,py311,312,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
Expand All @@ -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
Expand Down

0 comments on commit 65c350e

Please sign in to comment.