From 308850473f355b2be118c8de8e8a90f07a9aed36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Thu, 29 May 2025 10:12:11 -0400 Subject: [PATCH 01/12] Update setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index f54bda4..90f2763 100644 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ "Intended Audience :: Science/Research", "License :: OSI Approved :: Apache Software License", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Scientific/Engineering", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Software Development :: Libraries :: Python Modules", From 0217551d51a225cde18b8b6dd9de36ada9ee64a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Thu, 29 May 2025 10:19:15 -0400 Subject: [PATCH 02/12] Update docs.yml --- .github/workflows/docs.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 5f127e5..ed2b43f 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -23,7 +23,6 @@ jobs: python -m venv ./venv . ./venv/bin/activate python -m pip install --upgrade pip - pip install wheel build pip install poetry poetry install --with=docs --no-interaction --no-ansi From 532f728a281f2e379265e0802484261c49a6c650 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Thu, 29 May 2025 10:20:04 -0400 Subject: [PATCH 03/12] Update build_dist.yml --- .github/workflows/build_dist.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build_dist.yml b/.github/workflows/build_dist.yml index db2f4f4..1f732a1 100644 --- a/.github/workflows/build_dist.yml +++ b/.github/workflows/build_dist.yml @@ -87,3 +87,9 @@ jobs: with: tag_name: ${{ steps.tag.outputs.new_tag }} generate_release_notes: true + + - name: Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip_existing: true + verbose: true From 412dc8cc71d8b5680b2026b5002261366c5c4df4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Thu, 29 May 2025 10:37:57 -0400 Subject: [PATCH 04/12] Create py.typed --- src/python_template/py.typed | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/python_template/py.typed diff --git a/src/python_template/py.typed b/src/python_template/py.typed new file mode 100644 index 0000000..e69de29 From ff4a75de6889f7f03489182a1b08531966d41b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:46:44 -0400 Subject: [PATCH 05/12] update CICD --- .github/workflows/build_dist.yml | 31 ++++++----- .github/workflows/docs.yml | 6 --- .github/workflows/tests.yml | 33 +++++++++--- pyproject.toml | 5 -- run_pytests.py | 91 ++++++++++++++++++++++++-------- src/python_template/__main__.py | 10 ++-- 6 files changed, 118 insertions(+), 58 deletions(-) diff --git a/.github/workflows/build_dist.yml b/.github/workflows/build_dist.yml index 1f732a1..c34b047 100644 --- a/.github/workflows/build_dist.yml +++ b/.github/workflows/build_dist.yml @@ -41,37 +41,26 @@ jobs: run: | . ./venv/bin/activate poetry version ${{steps.version.outputs.new_tag}} - git config --local user.email "action@github.com" - git config --local user.name "GitHub Action" - git add pyproject.toml - git commit -m "Updating version of pyproject.toml" - - - name: Push the new pyproject.toml - uses: ad-m/github-push-action@master - with: - github_token: ${{secrets.GITHUB_TOKEN}} - branch: main - force: true - name: Build dist run: | . ./venv/bin/activate python -m build + twine check dist/* - - name: Commit the new dist + - name: Commit updated pyproject.toml run: | - git add -f dist git config --local user.email "action@github.com" git config --local user.name "GitHub Action" - git commit -m "Update dist" + git add pyproject.toml + git commit -m "Updating version of pyproject.toml" - - name: Push the new dist + - name: Push the new pyproject.toml uses: ad-m/github-push-action@master with: github_token: ${{secrets.GITHUB_TOKEN}} branch: main force: true - directory: dist - name: Create Tag id: tag @@ -93,3 +82,13 @@ jobs: with: skip_existing: true verbose: true + + - name: Merge 'main' branch -> 'dev' branch + uses: devmasx/merge-branch@master + with: + type: now + from_branch: main + target_branch: dev + message: "Automatic merge from 'main' into 'dev' [skip actions]" + label_name: "gh-actions" + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ed2b43f..ce261e1 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -61,9 +61,3 @@ jobs: branch: gh-pages force: true directory: docs - - - name: Publish distribution to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - skip_existing: true - verbose: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 241a248..828e500 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,13 +14,16 @@ jobs: Run-tests-on-Ubuntu: name: Run tests on Ubuntu-latest runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m venv ./venv @@ -46,18 +49,36 @@ jobs: - name: Test Unittests with pytest run: | . ./venv/bin/activate - python run_pytests.py tests --N_RANDOM_TESTS_PER_CASE=3 --run_slow=False + python run_pytests.py --tests_folder=tests --N_RANDOM_TESTS_PER_CASE=3 --no-run_slow --cov-report=xml:tests/.tmp/coverage.xml + + - name: Code Coverage + uses: orgoro/coverage@v3.2 + with: + token: ${{ secrets.GITHUB_TOKEN }} + coverageFile: tests/.tmp/coverage.xml + thresholdAll: 0.98 + thresholdNew: 0.98 + thresholdModified: 0.98 + + - name: Test Build + run: | + . ./venv/bin/activate + python -m build + twine check dist/* Run-tests-on-Windows: name: Run tests on Windows-latest runs-on: windows-latest + strategy: + matrix: + python-version: [ "3.10", "3.11", "3.12", "3.13" ] steps: - uses: actions/checkout@v3 - - name: Set up Python 3.10 + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v3 with: - python-version: "3.10" + python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m venv ./venv @@ -68,4 +89,4 @@ jobs: - name: Test Unittests with pytest run: | . ./venv/Scripts/activate - python run_pytests.py tests --N_RANDOM_TESTS_PER_CASE=3 --run_slow=False + python run_pytests.py --tests_folder=tests --N_RANDOM_TESTS_PER_CASE=1 --run_slow=False diff --git a/pyproject.toml b/pyproject.toml index 2429239..4756ed3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,11 +78,6 @@ sphinx-mdinclude = "^0.6.2" pythonpath = [ ".", "src", ] -addopts = [ - "--cov=src", - "--no-cov", - "--durations=10", -] [[tool.mypy.overrides]] module = [ diff --git a/run_pytests.py b/run_pytests.py index 8e00efd..d3bb4bc 100644 --- a/run_pytests.py +++ b/run_pytests.py @@ -8,31 +8,80 @@ from tests import configs from tests.conftest import RUN_SLOW_ARG_NAME +import argparse -if __name__ == '__main__': - # TODO: use argparse - sys_args_dict = pbt.cmds.get_cmd_kwargs( - { - 1 : os.path.join(os.getcwd(), "tests"), - "N_RANDOM_TESTS_PER_CASE": configs.N_RANDOM_TESTS_PER_CASE, - "save_report" : "True", - "cov" : "src", - "cov-report" : "xml", - RUN_SLOW_ARG_NAME : "True", - "durations" : 10, - } + +def get_args_parser(): + parser = argparse.ArgumentParser(description="Tests Runner") + parser.add_argument( + "--tests_folder", + type=str, + default=os.path.join(os.getcwd(), "tests"), + help="Path to the folder containing tests.", + ) + parser.add_argument( + "--N_RANDOM_TESTS_PER_CASE", + type=int, + default=configs.N_RANDOM_TESTS_PER_CASE, + help="Number of random tests to run per test case.", + ) + parser.add_argument( + "--save_report", + type=bool, + default=False, + action=argparse.BooleanOptionalAction, + help="Whether to save the report in JSON format.", + ) + parser.add_argument( + "--cov", + type=str, + default="src", + help="Path to the source code for coverage.", + ) + parser.add_argument( + "--cov-report", + type=str, + default="xml:tests/.tmp/coverage.xml", + help="Format of the coverage report.", + ) + parser.add_argument( + f"--{RUN_SLOW_ARG_NAME}", + type=bool, + default=False, + action=argparse.BooleanOptionalAction, + help="Whether to run slow tests.", ) - sys_args_dict["cov-report"] = sys_args_dict["cov_report"] # fix - configs.N_RANDOM_TESTS_PER_CASE = sys_args_dict["N_RANDOM_TESTS_PER_CASE"] - configs.RUN_SLOW_TESTS = 't' in str(sys_args_dict[RUN_SLOW_ARG_NAME]).lower() + parser.add_argument( + "--durations", + type=int, + default=10, + help="Number of slowest test durations to report.", + ) + return parser + + +def main(): + parser = get_args_parser() + args = parser.parse_args() + configs.N_RANDOM_TESTS_PER_CASE = args.N_RANDOM_TESTS_PER_CASE + configs.RUN_SLOW_TESTS = args.run_slow json_plugin = JSONReport() - pytest_main_args_names = ["cov", "cov-report", "durations"] - pytest_main_args = [f"--{k}={sys_args_dict[k]}" for k in pytest_main_args_names] - pytest.main([sys_args_dict[1], *pytest_main_args], plugins=[json_plugin]) - json_path = os.path.join(os.getcwd(), "tests", "tmp", f"tests_report_rn{configs.N_RANDOM_TESTS_PER_CASE}.json") - save_report = 't' in str(sys_args_dict["save_report"]).lower() - if save_report: + pytest_main_args = [ + args.tests_folder, + f"--cov={args.cov}", + f"--cov-report={args.cov_report}", + f"--cov-report=term-missing", + f"--durations={args.durations}", + ] + pytest.main(pytest_main_args, plugins=[json_plugin]) + json_path = os.path.join(args.tests_folder, ".tmp", f"tests_report_rn{configs.N_RANDOM_TESTS_PER_CASE}.json") + if args.save_report: json_plugin.save_report(json_path) json_data = json.load(open(json_path)) with open(json_path, "w") as f: json.dump(json_data, f, indent=4) + return 0 + + +if __name__ == '__main__': + exit(main()) diff --git a/src/python_template/__main__.py b/src/python_template/__main__.py index 1c45ec2..b985b1e 100644 --- a/src/python_template/__main__.py +++ b/src/python_template/__main__.py @@ -2,13 +2,15 @@ import sys -def parse_args(): - paser = argparse.ArgumentParser() - return paser.parse_args() +def get_args_parser(): + parser = argparse.ArgumentParser(description="Python Template") + return parser def main(): - pass + parser = get_args_parser() + args = parser.parse_args() + return 0 if __name__ == "__main__": From 9f92a37296e99830343ef9001f5b10a8e6ad90ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 20:50:21 -0400 Subject: [PATCH 06/12] Update tests.yml --- .github/workflows/tests.yml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 828e500..5385a7a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,15 +1,9 @@ -# This workflow will install Python dependencies, run tests and lint with a single version of Python -# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions - name: Tests on: pull_request: branches: [ "*" ] -permissions: - contents: read - jobs: Run-tests-on-Ubuntu: name: Run tests on Ubuntu-latest From b99f6696d387b185a6a703da2c3adf21d43d3a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:35:33 -0400 Subject: [PATCH 07/12] update tests --- pyproject.toml | 2 +- src/python_template/__init__.py | 2 +- src/python_template/__main__.py | 5 +++-- tests/test_dummy.py | 28 ++++++++++++++++++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 4756ed3..e337b0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "PythonTemplate" +name = "python_template" version = "0.0.1" dynamic = ["readme"] description = "" diff --git a/src/python_template/__init__.py b/src/python_template/__init__.py index 9ea2402..d5f68a0 100644 --- a/src/python_template/__init__.py +++ b/src/python_template/__init__.py @@ -6,7 +6,7 @@ __author__ = "Jeremie Gince" __email__ = "gincejeremie@gmail.com" -__copyright__ = "Copyright 2024, Jeremie Gince" +__copyright__ = "Copyright 2025, Jeremie Gince" __license__ = "Apache 2.0" __url__ = "https://github.com/JeremieGince/PythonProject-Template" __package__ = "python_template" diff --git a/src/python_template/__main__.py b/src/python_template/__main__.py index b985b1e..8e60d6e 100644 --- a/src/python_template/__main__.py +++ b/src/python_template/__main__.py @@ -1,5 +1,6 @@ import argparse import sys +from typing import Optional def get_args_parser(): @@ -7,9 +8,9 @@ def get_args_parser(): return parser -def main(): +def main(args: Optional[str] = None) -> Optional[int]: parser = get_args_parser() - args = parser.parse_args() + args = parser.parse_args(args) return 0 diff --git a/tests/test_dummy.py b/tests/test_dummy.py index 67690d3..c7b7bf8 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -1,6 +1,34 @@ import pytest +import python_template +from python_template.__main__ import main +import runpy @pytest.mark.parametrize("dummy", list(range(10))) def test_dummy(dummy): assert dummy < 10 + + +@pytest.mark.parametrize( + "attr", + [ + "__author__", + "__email__", + "__copyright__", + "__license__", + "__url__", + "__package__", + "__version__", + ], +) +def test_attributes(attr): + assert hasattr(python_template, attr), f"Module does not have attribute {attr}" + assert getattr(python_template, attr) is not None, f"Attribute {attr} is None" + assert isinstance( + getattr(python_template, attr), str + ), f"Attribute {attr} is not a string" + + +def test_main(): + exit_code = main("") + assert exit_code == 0, f"Main function did not return 0, got {exit_code}" From b126627612df676c701d0a93922e48176b0f47a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:51:40 -0400 Subject: [PATCH 08/12] add exclusions --- pyproject.toml | 12 ++++++++++++ tests/test_dummy.py | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e337b0d..4d15813 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -79,6 +79,18 @@ pythonpath = [ ".", "src", ] +[tool.coverage.report] +exclude_also = [ + 'def __repr__', + 'if self.debug:', + 'if settings.DEBUG', + 'raise AssertionError', + 'raise NotImplementedError', + 'if __name__ == .__main__.:', + 'if TYPE_CHECKING:', + '@(abc\.)?abstractmethod', +] + [[tool.mypy.overrides]] module = [ "pythonbasictools", diff --git a/tests/test_dummy.py b/tests/test_dummy.py index c7b7bf8..47bb2cb 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -1,6 +1,8 @@ +import argparse + import pytest import python_template -from python_template.__main__ import main +from python_template.__main__ import main, get_args_parser import runpy @@ -32,3 +34,11 @@ def test_attributes(attr): def test_main(): exit_code = main("") assert exit_code == 0, f"Main function did not return 0, got {exit_code}" + +def test_get_args_parser(): + parser = get_args_parser() + assert parser is not None, "get_args_parser returned None" + assert isinstance(parser, argparse.ArgumentParser), "get_args_parser did not return an ArgumentParser instance" + # Check if the parser has the expected attributes + assert hasattr(parser, "description"), "ArgumentParser does not have a description attribute" + assert parser.description == "Python Template", "ArgumentParser description does not match expected value" From cd0c53a940299bf13f66d0ea36a201a66044b3aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:52:44 -0400 Subject: [PATCH 09/12] Update test_dummy.py --- tests/test_dummy.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tests/test_dummy.py b/tests/test_dummy.py index 47bb2cb..d664e8e 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -35,10 +35,17 @@ def test_main(): exit_code = main("") assert exit_code == 0, f"Main function did not return 0, got {exit_code}" + def test_get_args_parser(): parser = get_args_parser() assert parser is not None, "get_args_parser returned None" - assert isinstance(parser, argparse.ArgumentParser), "get_args_parser did not return an ArgumentParser instance" + assert isinstance( + parser, argparse.ArgumentParser + ), "get_args_parser did not return an ArgumentParser instance" # Check if the parser has the expected attributes - assert hasattr(parser, "description"), "ArgumentParser does not have a description attribute" - assert parser.description == "Python Template", "ArgumentParser description does not match expected value" + assert hasattr( + parser, "description" + ), "ArgumentParser does not have a description attribute" + assert ( + parser.description == "Python Template" + ), "ArgumentParser description does not match expected value" From 6748f3f4cfa24d3ddb72102c14942ccad659a904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:56:42 -0400 Subject: [PATCH 10/12] Update tests.yml --- .github/workflows/tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5385a7a..1688fce 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -4,6 +4,9 @@ on: pull_request: branches: [ "*" ] +permissions: + contents: write + jobs: Run-tests-on-Ubuntu: name: Run tests on Ubuntu-latest From d4c742866c4a0bfbb2ec5c157679430548033bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 21:58:36 -0400 Subject: [PATCH 11/12] Update tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1688fce..3af8dde 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -86,4 +86,4 @@ jobs: - name: Test Unittests with pytest run: | . ./venv/Scripts/activate - python run_pytests.py --tests_folder=tests --N_RANDOM_TESTS_PER_CASE=1 --run_slow=False + python run_pytests.py --tests_folder=tests --N_RANDOM_TESTS_PER_CASE=1 --no-run_slow From bfeff42e4d53d8bd37e938a8806d69a20058f71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Gince?= <50332514+JeremieGince@users.noreply.github.com> Date: Mon, 2 Jun 2025 22:07:18 -0400 Subject: [PATCH 12/12] Update tests.yml --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3af8dde..5e4f0ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -6,6 +6,12 @@ on: permissions: contents: write + pull-requests: write + actions: write + checks: write + statuses: write + issues: write + discussions: write jobs: Run-tests-on-Ubuntu: