diff --git a/.github/workflows/build_dist.yml b/.github/workflows/build_dist.yml index db2f4f4..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 @@ -87,3 +76,19 @@ 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 + + - 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 5f127e5..ce261e1 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 @@ -62,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..5e4f0ac 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -1,6 +1,3 @@ -# 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: @@ -8,19 +5,28 @@ on: branches: [ "*" ] permissions: - contents: read + contents: write + pull-requests: write + actions: write + checks: write + statuses: write + issues: write + discussions: write 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 +52,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 +92,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 --no-run_slow diff --git a/pyproject.toml b/pyproject.toml index 2429239..4d15813 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [project] -name = "PythonTemplate" +name = "python_template" version = "0.0.1" dynamic = ["readme"] description = "" @@ -78,10 +78,17 @@ sphinx-mdinclude = "^0.6.2" pythonpath = [ ".", "src", ] -addopts = [ - "--cov=src", - "--no-cov", - "--durations=10", + +[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]] 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/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", 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 1c45ec2..8e60d6e 100644 --- a/src/python_template/__main__.py +++ b/src/python_template/__main__.py @@ -1,14 +1,17 @@ import argparse import sys +from typing import Optional -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 +def main(args: Optional[str] = None) -> Optional[int]: + parser = get_args_parser() + args = parser.parse_args(args) + return 0 if __name__ == "__main__": diff --git a/src/python_template/py.typed b/src/python_template/py.typed new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_dummy.py b/tests/test_dummy.py index 67690d3..d664e8e 100644 --- a/tests/test_dummy.py +++ b/tests/test_dummy.py @@ -1,6 +1,51 @@ +import argparse + import pytest +import python_template +from python_template.__main__ import main, get_args_parser +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}" + + +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"