From 05627f176d23637a7acbd4492303d3d33f1b3ebc Mon Sep 17 00:00:00 2001
From: Ronnie Dutta <61982285+MetRonnie@users.noreply.github.com>
Date: Fri, 20 Oct 2023 11:41:31 +0100
Subject: [PATCH] Switch to `pyproject.toml` (#240)
* Switch to `pyproject.toml
* Add pytest-xdist as test dependency & improve GH Actions
GH Actions:
- Use more processes in xdist for speedup
- Upload coverage to Codecov in separate job
- Add build test
- General
* Support python 3.
* Consolidate pytest & bandit configs into `pyproject.toml
* Coverage: improve exclusions
---
.bandit | 1 -
.coveragerc | 18 +++++-
.github/workflows/build.yml | 51 +++++++++++++++
.github/workflows/test.yml | 80 ++++++++++++++++-------
README.md | 9 +--
metomi/isodatetime/__init__.py | 2 +
pyproject.toml | 104 +++++++++++++++++++++++++++++
pytest.ini | 28 --------
setup.cfg | 24 -------
setup.py | 115 ---------------------------------
10 files changed, 233 insertions(+), 199 deletions(-)
delete mode 100644 .bandit
create mode 100644 .github/workflows/build.yml
create mode 100644 pyproject.toml
delete mode 100644 pytest.ini
delete mode 100644 setup.cfg
delete mode 100644 setup.py
diff --git a/.bandit b/.bandit
deleted file mode 100644
index 75d550c..0000000
--- a/.bandit
+++ /dev/null
@@ -1 +0,0 @@
-skips: ['B101']
diff --git a/.coveragerc b/.coveragerc
index 32c66d9..5d9c68a 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -27,10 +27,26 @@ source=
[report]
exclude_lines =
pragma: no cover
+
def __repr__
- raise NotImplementedError
+ @(abc\.)?abstractmethod
+
+ # Ignore code that can only run in CLI:
if __name__ == .__main__.:
+
+ # Don't complain if tests don't hit defensive assertion code:
+ raise NotImplementedError
+ return NotImplemented
+
+ # Ignore type checking code:
+ if (typing\.)?TYPE_CHECKING:
+ @overload( |$)
+
+ # Don't complain about ellipsis (exception classes, typing overloads etc):
+ \.\.\.
+
def parse_args
+
omit=
metomi/isodatetime/tests
precision=2
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..6f9a319
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,51 @@
+name: Build
+
+# build the project whenever the configuration is changed
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - 'README.md' # check markdown is valid
+ - 'MANIFEST.in' # check packaging
+ - 'pyproject.toml' # check build config
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ FORCE_COLOR: 2
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ timeout-minutes: 10
+ strategy:
+ matrix:
+ os: ['ubuntu-latest']
+ python: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
+ include:
+ - os: 'macos-latest'
+ python: '3.7'
+ name: ${{ matrix.os }} py-${{ matrix.python }}
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Python
+ uses: actions/setup-python@v4
+ with:
+ python-version: ${{ matrix.python }}
+
+ - name: Build
+ uses: cylc/release-actions/build-python-package@v1
+ with:
+ check-dependencies: false
+
+ - name: Inspect
+ run: |
+ unzip -l dist/*.whl | tee files
+ grep 'metomi/isodatetime/data.py' files
+ grep 'metomi_isodatetime.*.dist-info/LICENSE' files
+ # grep 'metomi/isodatetime/py.typed' files # (not yet added)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4ecfa8b..b8e5e6f 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -4,31 +4,41 @@ on:
pull_request:
push:
branches: [master]
+ workflow_dispatch:
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ FORCE_COLOR: 2
jobs:
test:
runs-on: ${{ matrix.os }}
- timeout-minutes: 45
+ timeout-minutes: 40
strategy:
matrix:
os: [ubuntu-latest]
- python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
+ python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12']
coverage: [false]
include:
# Modify existing configurations:
- os: ubuntu-latest
- python-version: '3.11'
+ python-version: '3.12'
coverage: false
tz: 'XXX-05:30' # UTC+05:30
# Add new configurations:
- os: ubuntu-latest
- python-version: '3.11'
+ python-version: '3.12'
coverage: true
- os: macos-latest
- python-version: '3.11'
+ python-version: '3.12'
coverage: false
+ name: ${{ matrix.os }} py-${{ matrix.python-version }} ${{ matrix.tz }} ${{ matrix.coverage && '(coverage)' || '' }}
+ env:
+ PYTEST_ADDOPTS: -n 5 -m 'slow or not slow'
steps:
-
- name: Checkout repo
uses: actions/checkout@v4
@@ -38,27 +48,53 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install
- run: |
- pip install --upgrade pip setuptools
- pip install -e .[all]
- pip install pytest-xdist
+ run: pip install -e .[all]
- - name: Style test
- run: flake8 metomi/isodatetime
+ - name: Style
+ if: ${{ !matrix.coverage }}
+ run: flake8
+
+ - name: Bandit
+ if: ${{ !matrix.coverage }}
+ run: bandit -r metomi/isodatetime -c pyproject.toml
- name: Run tests
env:
TZ: ${{ matrix.tz }}
- run: |
- PYTEST_ARGS=(-n 2 -m 'slow or not slow')
- if ${{ matrix.coverage }}; then
- PYTEST_ARGS+=('--cov=metomi/isodatetime')
- fi
+ PYTEST_ADDOPTS: ${{ matrix.coverage && format('{0} --cov=metomi/isodatetime', env.PYTEST_ADDOPTS) || env.PYTEST_ADDOPTS }}
+ run: pytest
- pytest "${PYTEST_ARGS[@]}"
-
- - name: Upload coverage report
+ - name: Coverage report
if: matrix.coverage
run: |
- coverage xml --ignore-errors
- bash <(curl -s https://codecov.io/bash)
+ coverage xml
+ coverage report
+
+ - name: Upload coverage artifact
+ if: matrix.coverage
+ uses: actions/upload-artifact@v3
+ with:
+ name: coverage_${{ matrix.os }}_py-${{ matrix.python-version }}
+ path: coverage.xml
+ retention-days: 4
+
+ codecov-upload:
+ needs: test
+ runs-on: ubuntu-latest
+ timeout-minutes: 2
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Download coverage artifacts
+ uses: actions/download-artifact@v3
+
+ - name: Codecov upload
+ uses: codecov/codecov-action@v3
+ with:
+ name: ${{ github.workflow }}
+ fail_ci_if_error: true
+ verbose: true
+ # Token not required for public repos, but avoids upload failure due
+ # to rate-limiting (but not for PRs opened from forks)
+ token: ${{ secrets.CODECOV_TOKEN }}
diff --git a/README.md b/README.md
index 2777be4..6b5477c 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,6 @@ isodatetime
[![Test](https://github.com/metomi/isodatetime/workflows/Test/badge.svg?event=push)](https://github.com/metomi/isodatetime/actions?query=workflow%3ATest)
[![codecov](https://codecov.io/gh/metomi/isodatetime/branch/master/graph/badge.svg)](https://codecov.io/gh/metomi/isodatetime)
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.597555.svg)](https://doi.org/10.5281/zenodo.597555)
-[![Codacy Badge](https://api.codacy.com/project/badge/Grade/1fd1147b75474d4d9a0f64bececf3bb5)](https://www.codacy.com/app/metomi/isodatetime?utm_source=github.com&utm_medium=referral&utm_content=metomi/isodatetime&utm_campaign=Badge_Grade)
[![PYPI Badge](https://img.shields.io/pypi/v/metomi-isodatetime)](https://pypi.org/project/metomi-isodatetime/)
Python [ISO8601 (2004)](https://www.iso.org/standard/40874.html)
@@ -19,13 +18,7 @@ Install from PyPI:
$ pip install metomi-isodatetime
```
-Or build yourself:
-
-```console
-$ git clone https://github.com/metomi/isodatetime.git isodatetime
-$ cd isodatetime
-$ python setup.py install
-```
+Or with conda: see https://github.com/conda-forge/metomi-isodatetime-feedstock
## Usage
diff --git a/metomi/isodatetime/__init__.py b/metomi/isodatetime/__init__.py
index 8e101bc..7379be8 100644
--- a/metomi/isodatetime/__init__.py
+++ b/metomi/isodatetime/__init__.py
@@ -18,3 +18,5 @@
"""Python ISO 8601 date time parser and data model/manipulation utilities."""
__version__ = "3.1.0"
+
+FULL_VERSION = f"1!{__version__}"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..ec58d08
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,104 @@
+# Copyright (C) British Crown (Met Office) & Contributors.
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program. If not, see .
+
+[build-system]
+requires = ["setuptools >= 49.0"]
+build-backend = "setuptools.build_meta"
+
+
+[project]
+name = "metomi-isodatetime"
+authors = [
+ {name = "Met Office", email = "metomi@metoffice.gov.uk"}
+]
+description = "Python ISO 8601 date time parser and data model/manipulation utilities"
+license = {text = "LGPLv3"}
+readme = "README.md"
+keywords = ["isodatetime", "datetime", "iso8601", "date", "time", "parser"]
+requires-python = ">=3.7"
+classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "Environment :: Other Environment",
+ "Intended Audience :: Developers",
+ "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
+ "Operating System :: OS Independent",
+ "Programming Language :: Python :: 3.7",
+ "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 :: 3 :: Only",
+ "Topic :: Software Development :: Libraries :: Python Modules",
+ "Topic :: Utilities"
+]
+dynamic = ["version"]
+
+
+[project.urls]
+Homepage = "https://github.com/metomi/isodatetime"
+Repository = "https://github.com/metomi/isodatetime"
+Changelog = "https://github.com/metomi/isodatetime/blob/master/CHANGES.md"
+
+
+[tool.setuptools]
+platforms = ["any"]
+
+
+[tool.setuptools.dynamic]
+version = {attr = "metomi.isodatetime.FULL_VERSION"}
+
+
+[tool.setuptools.packages.find]
+include = ["metomi*"]
+exclude = ["metomi.isodatetime.tests*"]
+
+
+[tool.setuptools.package-data]
+"metomi.isodatetime" = ["py.typed"]
+
+
+[project.scripts]
+isodatetime = "metomi.isodatetime.main:main"
+
+
+[project.optional-dependencies]
+test = [
+ "coverage",
+ "pytest>=6",
+ "pytest-env",
+ "pytest-cov",
+ "pytest-xdist",
+ "flake8",
+ "bandit>=1.7.1",
+]
+all = [
+ "metomi-isodatetime[test]",
+]
+
+
+[tool.pytest.ini_options]
+addopts = "-v -s -ra --color=auto --doctest-glob='*.md' -m 'not slow'"
+markers = [
+ "slow: mark a test as slow - it will be skipped by default (use '-m \"slow or not slow\"' to run all tests)"
+]
+testpaths = [
+ "metomi/isodatetime/tests",
+ "README.md",
+]
+
+
+[tool.bandit]
+exclude_dirs = ["metomi/isodatetime/tests"]
diff --git a/pytest.ini b/pytest.ini
deleted file mode 100644
index 3629552..0000000
--- a/pytest.ini
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see .
-
-[pytest]
-addopts =
- -v
- -s
- -ra
- --color=auto
- --doctest-glob='*.md'
- -m 'not slow'
-markers =
- slow: mark a test as slow - it will be skipped by default (use '-m "slow or not slow"' to run all tests)
-testpaths =
- metomi/isodatetime/tests
- README.md
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index dff6bae..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,24 +0,0 @@
-# -*- coding: utf-8 -*-
-# ----------------------------------------------------------------------------
-# Copyright (C) British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see .
-# ----------------------------------------------------------------------------
-
-[aliases]
-# so that running python setup.py test invokes pytest
-test=pytest
-
-[metadata]
-license_files = LICENSE
diff --git a/setup.py b/setup.py
deleted file mode 100644
index c97eece..0000000
--- a/setup.py
+++ /dev/null
@@ -1,115 +0,0 @@
-# -*- coding: utf-8 -*-
-# ----------------------------------------------------------------------------
-# Copyright (C) British Crown (Met Office) & Contributors.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Lesser General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public License
-# along with this program. If not, see .
-# ----------------------------------------------------------------------------
-
-from importlib.util import spec_from_file_location, module_from_spec
-from pathlib import Path
-# overriding setuptools command
-# https://stackoverflow.com/a/51294311
-from setuptools.command.bdist_rpm import bdist_rpm as bdist_rpm_original
-# to parse pytest command arguments
-# https://docs.pytest.org/en/2.8.7/goodpractices.html#manual-integration
-from setuptools import setup, find_namespace_packages
-
-
-DIST_DIR = Path(__file__).resolve().parent
-
-
-def get_version(module, path):
- """Return the __version__ attr from a module sourced by FS path."""
- spec = spec_from_file_location(module, path)
- module = module_from_spec(spec)
- spec.loader.exec_module(module)
- return module.__version__
-
-
-__version__ = get_version(
- 'metomi.isodatetime',
- str(Path(DIST_DIR, 'metomi/isodatetime/__init__.py'))
-)
-
-
-class bdist_rpm(bdist_rpm_original):
-
- def run(self):
- """Before calling the original run method, let's change the
- distribution name to create an RPM for python-isodatetime."""
- self.distribution.metadata.name = "python-isodatetime"
- self.distribution.metadata.version = __version__
- super().run()
-
-
-install_requires = []
-tests_require = [
- 'coverage',
- 'pytest>=5',
- 'pytest-env',
- 'pytest-cov',
- 'flake8'
-]
-extras_require = {}
-extras_require['all'] = (
- tests_require
- + list({
- req
- for reqs in extras_require.values()
- for req in reqs
- })
-)
-
-
-setup(
- name="metomi-isodatetime",
- version='1!' + __version__,
- author="Met Office",
- author_email="metomi@metoffice.gov.uk",
- cmdclass={
- "bdist_rpm": bdist_rpm
- },
- description=("Python ISO 8601 date time parser" +
- " and data model/manipulation utilities"),
- license="LGPLv3",
- keywords="isodatetime datetime iso8601 date time parser",
- url="https://github.com/metomi/isodatetime",
- packages=find_namespace_packages(include=['metomi.*']),
- long_description=open(str(Path(DIST_DIR, 'README.md')), 'r').read(),
- long_description_content_type="text/markdown",
- platforms='any',
- install_requires=install_requires,
- tests_require=tests_require,
- extras_require=extras_require,
- python_requires='>=3.7',
- classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Environment :: Other Environment",
- "Intended Audience :: Developers",
- ("License :: OSI Approved" +
- " :: GNU Lesser General Public License v3 (LGPLv3)"),
- "Operating System :: OS Independent",
- "Programming Language :: Python :: 3.7",
- "Programming Language :: Python :: 3.8",
- "Programming Language :: Python :: 3.9",
- "Programming Language :: Python :: 3.10",
- "Programming Language :: Python :: 3.11",
- "Programming Language :: Python :: 3 :: Only",
- "Topic :: Software Development :: Libraries :: Python Modules",
- "Topic :: Utilities"
- ],
- entry_points={
- 'console_scripts': ['isodatetime=metomi.isodatetime.main:main'],
- },
-)