diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c38c32de..b867e081 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -83,9 +83,9 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install wheel twine setuptools + pip install build twine - name: Build distribution files - run: python setup.py bdist_wheel sdist + run: python -m build # same as in deploy-tag-to-pypi - name: Check distribution files run: twine check dist/* @@ -115,22 +115,11 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install wheel twine setuptools - - name: Check the tag - run: | - PACKAGE_VERSION=`python setup.py --version` - TAG_NAME=v$PACKAGE_VERSION - echo "Package version $PACKAGE_VERSION with possible tag name $TAG_NAME on $GITHUB_REF_NAME" - # test that the tag represents the version - # see https://docs.github.com/en/actions/learn-github-actions/environment-variables - if [ "$TAG_NAME" != "$GITHUB_REF_NAME" ]; then - echo "ERROR: This tag is for the wrong version. Got \"$GITHUB_REF_NAME\" expected \"$TAG_NAME\"." - exit 1 - fi + pip install build twine - name: remove old files run: rm -rf dist/* - name: build distribution files - run: python setup.py bdist_wheel sdist + run: python -m build # same as in test-distribution - name: deploy to pypi run: | # You will have to set the variables TWINE_USERNAME and TWINE_PASSWORD diff --git a/.gitignore b/.gitignore index 61c8bde7..533cecce 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ src/icalendar.egg-info/ !.gitignore venv /ical_fuzzer.pkg.spec +/src/icalendar/_version.py + +coverage.xml diff --git a/CHANGES.rst b/CHANGES.rst index 2d01f680..ff737b39 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -6,7 +6,7 @@ Changelog Minor changes: -- add ``__all__`` variable to each modules in ``icalendar`` package +- Add ``__all__`` variable to each modules in ``icalendar`` package - Improve test coverage. - Adapt ``test_with_doctest.py`` to correctly run on Windows. - Measure branch coverage when running tests. @@ -18,7 +18,7 @@ Breaking changes: New features: -- ... +- Use ``pyproject.toml`` file instead of ``setup.py`` Bug fixes: diff --git a/README.rst b/README.rst index 3c7b2900..17cb6433 100644 --- a/README.rst +++ b/README.rst @@ -176,3 +176,16 @@ Related projects * `icalevents `_. It is built on top of icalendar and allows you to query iCal files and get the events happening on specific dates. It manages recurrent events as well. * `recurring-ical-events `_. Library to query an ``ICalendar`` object for events happening at a certain date or within a certain time. * `x-wr-timezone `_. Library to make ``ICalendar`` objects and files using the non-standard ``X-WR-TIMEZONE`` compliant with the standard (RFC 5545). + +Further Reading +=============== + +You can find out more about this project: + +* `Contributing`_ +* `Changelog`_ +* `License`_ + +.. _`Contributing`: https://icalendar.readthedocs.io/en/latest/contributing.html +.. _`Changelog`: https://icalendar.readthedocs.io/en/latest/changelog.html +.. _`License`: https://icalendar.readthedocs.io/en/latest/license.html diff --git a/docs/install.rst b/docs/install.rst index 4349bea3..6a1e9a21 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -71,6 +71,16 @@ This is how you can run ``tox`` with Python 3.9: tox -e py39 +Code Style +---------- + +We strive towards a common code style. +You can run the following command to auto-format the code. + +.. code-block:: shell + + tox -e ruff + Accessing a ``tox`` environment ------------------------------- diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..6502e788 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,191 @@ +# +# We use the pyproject.toml package specification. +# See https://packaging.python.org/en/latest/guides/section-build-and-publish/ +# See https://github.com/collective/icalendar/issues/686 +# See https://packaging.python.org/en/latest/specifications/pyproject-toml/ +# + +[build-system] +requires = ["hatchling", "hatch-vcs"] +build-backend = "hatchling.build" + +[project] +name = "icalendar" +license = { file = "LICENSE.rst", name = "BSD-2-Clause" } +# name = "BSD-2-Clause", # TODO: is this the right short key +keywords = [ + "calendar", + "calendaring", + "ical", + "icalendar", + "event", + "todo", + "journal", + "recurring", + "rfc5545", +] +# This email is not in use any more. If you find a better one, go ahead! +# See https://github.com/collective/icalendar/pull/707#discussion_r1775275335 +authors = [ + { name="Plone Foundation", email="plone-developers@lists.sourceforge.net" }, +] +maintainers = [ + { name="Nicco Kunzmann", email="niccokunzmann@rambler.ru" }, + { name="Christian Geier" }, + { name="Jaca", email="vitouejj@gmail.com" }, +] +# These attributes are dynamically generated by hatch-vcs +dynamic = [ + "urls", + "version" +] +description = "iCalendar parser/generator" +readme = { file = "README.rst", content-type = "text/x-rst" } + +# +# When adjusting the Python Version, adjust also: +# - .github/workflows/tests.yml +# - the classifiers below +# - the documentation +# - the README file +# - dependencies for 3.8, Python 3.13 will come our October, too +# - tool.ruff.target-version +# +requires-python = ">=3.8" + +# see https://pypi.python.org/pypi?%3Aaction=list_classifiers +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: BSD License", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + +dependencies = [ + "python-dateutil", + # install requirements depending on python version, TODO: Remove with Python 3.8 + # see https://www.python.org/dev/peps/pep-0508/#environment-markers + "backports.zoneinfo; python_version < '3.9'", + "tzdata" +] + +[project.optional-dependencies] +test = [ + "pytest", + "coverage", + "hypothesis", + "pytz", +] + +[project.scripts] +icalendar = "icalendar.cli:main" + +[tool.hatch.build] +exclude = [ + "/.*", + "/*.*", + "/src/icalendar/fuzzing", + "/docs", + "/dist", + "/build", + "/htmlcov", +] + +[tool.hatch.metadata.hooks.vcs.urls] +# This is a dynamic generation of [project.urls] +Homepage = "https://icalendar.readthedocs.io/" +Repository = "https://github.com/collective/icalendar/" +source_archive = "https://github.com/collective/icalendar/archive/{commit_hash}.zip" +Issues = "https://github.com/collective/icalendar/issues" +Documentation = "https://icalendar.readthedocs.io/" +Changelog = "https://icalendar.readthedocs.io/en/latest/changelog.html" + +[tool.hatch.version] +# This configuration allows us to use the version from the tags and dynamically generate +# version files. This speeds up the release process. +source = "vcs" + +[tool.hatch.version.raw-options] +# see https://github.com/ofek/hatch-vcs/issues/43#issuecomment-1553065222 +local_scheme = "no-local-version" + +[tool.hatch.build.hooks.vcs] +version-file = "src/icalendar/_version.py" + +[tool.hatch.metadata] +allow-direct-references = true + +[tool.ruff] +target-version = "py38" + +[tool.ruff.lint] +select = ["ALL"] +ignore = [ + "ANN", # flake8-annotations + "B020", # Loop control variable {name} overrides iterable it iterates + "C401", # Unnecessary generator (rewrite as a set comprehension) + "C901", # {name} is too complex ({complexity} > {max_complexity}) + "COM812", # Trailing comma missing + "D1", # Missing docstring + "D2", # docstrings stuffs + "D4", # docstrings stuffs + "EM10", # Exception string usage + "ERA001", # Found commented-out code + "FBT002", # Boolean default positional argument in function definition + "FIX", # TODO comments + "ISC001", # Implicitly concatenated string literals on one line (to avoid with formatter) + "N818", # Exception name {name} should be named with an Error suffix + "PLR091", # Too many things (complexity, arguments, branches, etc...) + "PLR2004", # Magic value used in comparison, consider replacing {value} with a constant variable + "RUF012", # Mutable class attributes should be annotated with typing.ClassVar + "RUF015", # Prefer next({iterable}) over single element slice + "S101", # Use of assert detected + "TD", # TODO comments + "TRY003", # Avoid specifying long messages outside the exception class + "S104", # Possible binding to all interfaces + "E722", # Do not use bare `except` + "RUF005", # Consider iterable unpacking instead of concatenation + "DTZ005", # `datetime.datetime.now()` called without a `tz` argument + "PERF401", # Use a list comprehension to create a transformed list + "ARG002", # Unused method argument: ... + "ARG001", # Unused function argument: ... +] +extend-safe-fixes = [ + "PT006", # Wrong type passed to first argument of @pytest.mark.parametrize; expected {expected_string} +] + +[tool.ruff.lint.per-file-ignores] +"src/icalendar/tests/*" = [ + "B011", # Do not assert False (python -O removes these calls), raise AssertionError() + "DTZ001", # datetime.datetime() called without a tzinfo argument + "E501", # Indentation is not a multiple of {indent_size} + "N802", # Function name {name} should be lowercase + "PT011", # pytest.raises({exception}) is too broad, set the match parameter or use a more specific exception + "PT012", # pytest.raises() block should contain a single simple statement + "PT015", # Assertion always fails, replace with pytest.fail() + "T201", # print found + "T203", # `pprint` found + "RUF001", # String contains ambiguous character +] + +[tool.pytest.ini_options] +# see https://docs.pytest.org/en/6.2.x/customize.html +minversion = "6.0" +# see https://docs.pytest.org/en/6.2.x/reference.html?highlight=testpaths#confval-testpaths +testpaths = [ + "src/icalendar/tests", +] +# see https://docs.pytest.org/en/6.2.x/reference.html?highlight=testpaths#confval-norecursedirs +norecursedirs = [ + "src/icalendar/tests/hypothesis", + "build", +] + diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 051c5c69..00000000 --- a/setup.cfg +++ /dev/null @@ -1,18 +0,0 @@ -[check-manifest] -ignore = - *.cfg - .readthedocs.yml - bootstrap.py - requirements_docs.txt - -[zest.releaser] -python-file-with-version = src/icalendar/__init__.py - -[bdist_wheel] -universal = 0 - -[metadata] -license_files = LICENSE.rst - -[tool:pytest] -norecursedirs = .* env* docs *.egg src/icalendar/tests/hypothesis diff --git a/setup.py b/setup.py deleted file mode 100644 index 016ce0d9..00000000 --- a/setup.py +++ /dev/null @@ -1,67 +0,0 @@ -import codecs -import setuptools -import re -import ast - -_version_re = re.compile(r'__version__\s+=\s+(.*)') - -with open('src/icalendar/__init__.py', 'rb') as f: - version = str(ast.literal_eval(_version_re.search( - f.read().decode('utf-8')).group(1))) - - -shortdesc = 'iCalendar parser/generator' -longdesc = '' -for fname in ['README.rst', 'CONTRIBUTING.rst', 'CHANGES.rst', 'LICENSE.rst']: - with codecs.open(fname, encoding='utf-8') as f: - longdesc += f.read() - longdesc += '\n' - -tests_require = [] -install_requires = [ - 'python-dateutil', - # install requirements depending on python version - # see https://www.python.org/dev/peps/pep-0508/#environment-markers - 'backports.zoneinfo; python_version < "3.9"', - 'tzdata' -] - - -setuptools.setup( - name='icalendar', - version=version, - description=shortdesc, - long_description=longdesc, - long_description_content_type="text/x-rst", - classifiers=[ # https://pypi.python.org/pypi?%3Aaction=list_classifiers - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: BSD License', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Programming Language :: Python :: 3.11', - 'Programming Language :: Python :: 3.12', - 'Programming Language :: Python :: Implementation :: CPython', - 'Programming Language :: Python :: Implementation :: PyPy', - ], - keywords='calendar calendaring ical icalendar event todo journal ' - 'recurring', - author='Plone Foundation', - author_email='plone-developers@lists.sourceforge.net', - url='https://github.com/collective/icalendar', - license='BSD-2-Clause', - packages=setuptools.find_namespace_packages('src', exclude=["icalendar.fuzzing"]), - package_dir={'': 'src'}, - include_package_data=True, - zip_safe=False, - python_requires=">=3.8", - install_requires=install_requires, - entry_points = {'console_scripts': ['icalendar = icalendar.cli:main']}, - extras_require={ - 'test': tests_require - }, - test_suite='icalendar.tests' -) diff --git a/src/icalendar/tests/test_create_release.sh b/src/icalendar/tests/test_create_release.sh index 3f7f66a6..447694d6 100755 --- a/src/icalendar/tests/test_create_release.sh +++ b/src/icalendar/tests/test_create_release.sh @@ -7,7 +7,9 @@ set -e cd "`dirname \"$0\"`" cd "../../.." -python3 setup.py sdist +rm -rf dist +pip3 install build +python3 -m build archive=`echo dist/icalendar-*.tar.gz` if ! [ -f "$archive" ]; then diff --git a/src/icalendar/version.py b/src/icalendar/version.py new file mode 100644 index 00000000..23690077 --- /dev/null +++ b/src/icalendar/version.py @@ -0,0 +1,13 @@ +"""Version file as a stable interface for the generated _version.py file.""" +try: + from ._version import __version__, __version_tuple__, version, version_tuple +except ModuleNotFoundError: + __version__ = version = "0.0.0dev0" + __version_tuple__ = version_tuple = (0, 0, 0, "dev0") + +__all__ = [ + "__version__", + "version", + "__version_tuple__", + "version_tuple", +] diff --git a/tox.ini b/tox.ini index cb9ce787..4f43b519 100644 --- a/tox.ini +++ b/tox.ini @@ -8,10 +8,7 @@ envlist = py38,py39,py310,py311,312,pypy3,docs,nopytz [testenv] usedevelop=True deps = - pytest - coverage - hypothesis - pytz + -e .[test] commands = coverage run --branch --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest [] coverage report @@ -23,15 +20,11 @@ commands = 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 --branch --source=src/icalendar --omit=*/tests/hypothesis/* --omit=*/tests/fuzzed/* --module pytest [] coverage report coverage html @@ -45,3 +38,11 @@ changedir = docs allowlist_externals = make commands = make html + +[testenv:ruff] +# use tox -e ruff to format the code base +deps = ruff +skip_install = True +commands = + ruff format + ruff check --fix