Skip to content

Commit 5db54df

Browse files
authored
[NEW] Python Generator v2.5.2
Release v2.5.2
2 parents 41ebf57 + 4250f18 commit 5db54df

File tree

17 files changed

+528
-132
lines changed

17 files changed

+528
-132
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,13 @@ jobs:
116116
## Unless override, default trigger is (only) on PR to dev
117117
sca:
118118
# Unless override, default trigger is (only) on PR to dev
119-
if: always() || vars.OV_SCA == 'true' || (vars.OV_SCA != 'false' && github.event_name == 'pull_request' && github.base_ref == 'dev')
119+
if: vars.OV_SCA == 'true' || (vars.OV_SCA != 'false' && github.event_name == 'pull_request' && github.base_ref == 'dev')
120120
uses: ./.github/workflows/sca-job.yml
121121
with:
122122
python_version: '3.10'
123123
allow_failure: ${{ github.event_name != 'pull_request' || github.base_ref != 'dev' }}
124124
force_styles: ${{ github.event_name == 'pull_request' && github.base_ref == 'dev' }}
125-
bandit: '{"h": 1, "m": 2, "l": 4}' # Automated Acceptance Criteria for Bandit
125+
bandit: '{"h": "0", "m": "0", "l": "4"}' # Automated Acceptance Criteria for Bandit
126126

127127
# RUN INTEGRATION TESTS: slower due to automated installations involved via pip
128128
## Unless override, default trigger is (only) on PR to dev
@@ -175,13 +175,13 @@ jobs:
175175
# CROSS PLATFORM TESTING: 15s on Ubuntu, 25 on mac, 35 on windows
176176
## Unless override, default trigger is (only) on PR to dev
177177
sample_matrix_test:
178-
if: vars.OV_MATRIX_TEST == 'true' || (vars.OV_MATRIX_TEST != 'false' && github.event_name == 'pull_request' && github.base_ref == 'dev')
178+
if: always() || vars.OV_MATRIX_TEST == 'true' || (vars.OV_MATRIX_TEST != 'false' && github.event_name == 'pull_request' && github.base_ref == 'dev')
179179
runs-on: ${{ matrix.platform }}
180180
# trigger if event is pr to dev
181181
strategy:
182182
fail-fast: false
183183
matrix:
184-
platform: [ubuntu-latest, macos-latest, windows-latest]
184+
platform: [macos-latest, ubuntu-latest, windows-latest]
185185
# platform: [windows-latest]
186186
python-version: ['3.10']
187187
steps:
@@ -236,7 +236,7 @@ jobs:
236236
echo "[INFO] Activating virtual environment for Linux/MacOS"
237237
. .venv/bin/activate
238238
fi
239-
pytest -ra --run-requires_uv --run-network_bound -vvs -k 'context_with_expected_values[gold_standard]'
239+
pytest -ra --run-requires_uv --run-network_bound -vvs
240240
241241
## PYDEPS
242242

.github/workflows/dev_pr_validation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ jobs:
194194
python_version: '3.10'
195195
allow_failure: ${{ github.event_name != 'pull_request' || github.base_ref != 'dev' }}
196196
force_styles: ${{ github.event_name == 'pull_request' && github.base_ref == 'dev' }}
197-
bandit: '{"h": 1, "m": 2, "l": 4}' # Automated Acceptance Criteria for Bandit
197+
bandit: '{"h": "0", "m": "0", "l": "4"}' # Automated Acceptance Criteria for Bandit
198198

199199
# PYDEPS
200200
pydeps:

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ jobs:
201201
python_version: '3.10'
202202
allow_failure: false
203203
force_styles: true
204-
bandit: '{"h": 1, "m": 2, "l": 4}' # Automated Acceptance Criteria for Bandit
204+
bandit: '{"h": "0", "m": "2", "l": "4"}' # Automated Acceptance Criteria for Bandit
205205

206206
### DOCS BUILD/TEST - DOCUMENTATION SITE ###
207207
docs:

CHANGELOG.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ Changelog
33
=========
44

55

6+
2.5.2 (2024-04-14)
7+
==================
8+
9+
**Mutants elimination** and **CWEs mitigation**
10+
11+
612
2.5.1-dev (2024-04-12)
713
======================
814

README.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,9 @@ Free/Libre and Open Source Software (FLOSS)
275275

276276
.. Github Releases & Tags
277277
278-
.. |commits_since_specific_tag_on_master| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/v2.5.1-dev/master?color=blue&logo=github
278+
.. |commits_since_specific_tag_on_master| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/v2.5.2/master?color=blue&logo=github
279279
:alt: GitHub commits since tagged version (branch)
280-
:target: https://github.com/boromir674/cookiecutter-python-package/compare/v2.5.1-dev..master
280+
:target: https://github.com/boromir674/cookiecutter-python-package/compare/v2.5.2..master
281281

282282
.. |commits_since_latest_github_release| image:: https://img.shields.io/github/commits-since/boromir674/cookiecutter-python-package/latest?color=blue&logo=semver&sort=semver
283283
:alt: GitHub commits since latest release (by SemVer)

docs/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
author = 'Konstantinos Lampridis'
3131

3232
# The full version, including alpha/beta/rc tags
33-
release = '2.5.1-dev'
33+
release = '2.5.2'
3434

3535
# -- General configuration ---------------------------------------------------
3636

pyproject.toml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ build-backend = "poetry.core.masonry.api"
1414
name = "cookiecutter_python"
1515
### ... ###
1616

17-
version = "2.5.1-dev"
17+
version = "2.5.2"
1818
description = "1-click Generator of Python Project, from Template with streamlined \"DevOps\" using a powerful CI/CD Pipeline."
1919
readme = "README.rst"
2020
license = "AGPL-3.0-only"
@@ -74,7 +74,7 @@ maintainers = [
7474
license = {text = "AGPL-3.0-only"}
7575

7676
name = "cookiecutter_python"
77-
version = "2.5.1-dev"
77+
version = "2.5.2"
7878
description = "1-click Generator of Python Project, from Template with streamlined \"DevOps\" using a powerful CI/CD Pipeline."
7979
readme = "README.rst"
8080
# keywords = []
@@ -179,7 +179,27 @@ explicit-only = [
179179
]
180180

181181

182+
### BANDIT
183+
184+
[tool.bandit]
185+
exclude_dirs = ["tests/data", "path/to/file"]
186+
tests = []
187+
skips = [
188+
"B101",
189+
]
190+
191+
182192

183193
[tool.isort]
184194
profile = 'black'
185195
lines_after_imports = 2
196+
197+
198+
199+
### MUTATION TESTS ###
200+
[tool.mutmut]
201+
tests_dir = "tests/"
202+
runner = "python -m pytest -n auto"
203+
204+
paths_to_mutate = "src/cookiecutter_python/utils.py"
205+

scripts/lint-local.sh

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env sh
2+
3+
# Run Linters and modify in place !
4+
5+
set -e
6+
7+
8+
# find all .yml or .yaml files in template dir and verify valid yaml
9+
10+
SEARCH_DIR='tests/data/snapshots/'
11+
12+
13+
echo DONE
14+
15+
# V2 for any posix-compliant shell
16+
find "${SEARCH_DIR}" -type f \( -name "*.yml" -o -name "*.yaml" \) | while read yaml_file; do
17+
if [ ! -f "$yaml_file" ]; then
18+
echo "ERROR: $yaml_file does not exist"
19+
exit 1
20+
fi
21+
22+
# Check if the file is a valid YAML file
23+
if ! yq eval '.' "$yaml_file" > /dev/null 2>&1; then
24+
echo "ERROR: $yaml_file is not a valid YAML file"
25+
exit 1
26+
fi
27+
done
28+
29+
30+
# Require Clean Git Working Dir Status
31+
32+
if [ -n "$(git status --porcelain -uno)" ]; then
33+
echo "Git Working Directory is not clean!"
34+
echo "Please commit or stash your changes before running this script."
35+
exit 1
36+
fi
37+
38+
LINT_EXCLUDE='tests/data/snapshots'
39+
LINT_ARGS="src tests scripts"
40+
41+
uv venv .lint-env
42+
. .lint-env/bin/activate
43+
uv pip install 'isort>=5.12.0, <6.0.0' 'black>=23.3.0, <24.0.0' 'ruff' 'prospector[with_pyroma]'
44+
45+
## APPLY ISORT ##
46+
echo "[INFO] Running Isort..."
47+
uv run --active isort --skip tests/data/snapshots \
48+
--skip 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' \
49+
${LINT_ARGS}
50+
51+
52+
# if files changed (git diff), then add and commit
53+
if [ -n "$(git status --porcelain -uno)" ]; then
54+
git add -u
55+
git commit -m "refactor(isort): apply isort"
56+
fi
57+
58+
## APPLY BLACK ##
59+
echo "[INFO] Running Black..."
60+
uv run --active black \
61+
--skip-string-normalization \
62+
--exclude tests/data/snapshots \
63+
--extend-exclude 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' \
64+
--config pyproject.toml \
65+
${LINT_ARGS}
66+
67+
68+
# if files changed (git diff), then add and commit
69+
if [ -n "$(git status --porcelain -uno)" ]; then
70+
git add -u
71+
git commit -m "refactor(black): apply black"
72+
fi
73+
74+
## APPLY RUFF ##
75+
echo "[INFO] Running Ruff..."
76+
uv run --active ruff check --fix \
77+
--extend-exclude 'src/cookiecutter_python/\{\{\ cookiecutter.project_slug\ \}\}' \
78+
${LINT_ARGS}
79+
80+
# if files changed (git diff), then add and commit
81+
if [ -n "$(git status --porcelain -uno)" ]; then
82+
git add -u
83+
git commit -m "refactor(ruff): apply ruff"
84+
fi
85+
86+
set +e
87+
88+
uv run --active isort --check --skip tests/data/snapshots --skip 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' ${LINT_ARGS}
89+
uv run --active black --check --skip-string-normalization --exclude tests/data/snapshots --extend-exclude 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' --config pyproject.toml ${LINT_ARGS}
90+
uv run --active ruff check --extend-exclude 'src/cookiecutter_python/\{\{\ cookiecutter.project_slug\ \}\}' ${LINT_ARGS}
91+
92+
uv run --active prospector src
93+
uv run --active prospector tests
94+
95+
set -e
96+
97+
echo
98+
echo " ---> Linters applied !!"
99+
echo
100+
echo "For Final Sanity, run:"
101+
echo
102+
# echo "tox -e isort && tox -e black && tox -e ruff && tox -e prospector && tox -e pin-deps -- -E typing && tox -e type"
103+
echo ". .lint-env/bin/activate && export LINT_EXCLUDE='tests/data/snapshots' && export LINT_ARGS='src tests scripts' && uv run --active isort --check --skip tests/data/snapshots --skip 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' ${LINT_ARGS} && uv run --active black --check --skip-string-normalization --exclude tests/data/snapshots --extend-exclude 'src/cookiecutter_python/{{ cookiecutter.project_slug }}/' --config pyproject.toml ${LINT_ARGS} && uv run --active ruff check --extend-exclude 'src/cookiecutter_python/\{\{\ cookiecutter.project_slug\ \}\}' ${LINT_ARGS} && uv run --active prospector src && uv run --active prospector tests"
104+
echo
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
__version__ = '2.5.1-dev'
1+
__version__ = '2.5.2'
22

33
from . import _logging # noqa

src/cookiecutter_python/utils.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22
from importlib import import_module
33
from inspect import isclass
44
from os import path
5+
from pathlib import Path
56
from pkgutil import iter_modules
67
from typing import List, Optional, Type, TypeVar
78

89

10+
SRC_DIR = Path(__file__).parent.parent.resolve()
11+
912
T = TypeVar('T')
1013

1114

@@ -27,35 +30,42 @@ def load(interface: Type[T], module: Optional[str] = None) -> List[Type[T]]:
2730
Args:
2831
interface (Type[T]): the type (ie class) that the imported classes
2932
should 'inherit' (subclass) from
30-
module (str): module containing the modules to inspect. Defaults to the
33+
module (str): module dotted-path containing the modules to inspect. Defaults to the
3134
same module (directory) as the one where the module of the invoking
3235
code resides.
3336
"""
34-
project_package_location = path.dirname(
35-
path.realpath(path.dirname(path.realpath(__file__)))
36-
)
37+
lib_dir: str
38+
dotted_lib_path: str #
3739
if module is None: # set path as the dir where the invoking code is
3840
namespace = sys._getframe(1).f_globals # caller's globals
39-
directory: str = path.dirname(path.realpath(namespace['__file__']))
40-
relative_path = path.relpath(directory, start=project_package_location)
41-
_module = relative_path.replace('\\', '/').replace('/', '.')
41+
# Set as Lib the directory where the invoker module is located at runtime
42+
lib_dir = path.dirname(path.realpath(namespace['__file__']))
43+
dotted_lib_path = '.'.join(
44+
Path(lib_dir).relative_to(SRC_DIR).parts
45+
) # pragma: no mutate
4246
else:
43-
directory = str(module).replace('/', '.')
44-
# find the distro path as installed at runtime
45-
module_object = import_module(directory)
47+
# Import input module
48+
# module_object = import_module(module.replace('/', '.'))
49+
module_object = import_module(module) # TODO: read __file__ without importing
50+
51+
# Set as Lib the directory where the INPUT module is located at runtime
52+
lib_dir = str(Path(str(module_object.__file__)).parent)
4653
# if top-level init is at '/site-packages/some_python_package/__init__.py'
4754
# then distro_path is '/site-packages/some_python_package'
48-
from pathlib import Path
4955

50-
distro_path: Path = Path(str(module_object.__file__)).parent
51-
directory = str(distro_path)
52-
_module = module
56+
dotted_lib_path = module
57+
58+
if not Path(lib_dir).exists():
59+
raise FileNotFoundError
5360

5461
objects = []
55-
# iterate through the modules inside the directory
56-
for _, module_name, _ in iter_modules([directory]):
62+
63+
# iterate through the modules inside the LIB directory
64+
for _, module_name, _ in iter_modules([lib_dir]):
65+
# if module has a register_as_subclass decorator then the below import
66+
# will cause the class to be registered in the Facility/Factory Registry
5767
module_object = import_module(
58-
'{package}.{module}'.format(package=_module, module=module_name)
68+
'{package}.{module}'.format(package=dotted_lib_path, module=module_name)
5969
)
6070

6171
for attribute_name in dir(module_object):
@@ -66,7 +76,5 @@ def load(interface: Type[T], module: Optional[str] = None) -> List[Type[T]]:
6676
and isclass(attribute)
6777
and issubclass(attribute, interface)
6878
):
69-
# Add the class to this package's variables
70-
globals()[attribute_name] = attribute
7179
objects.append(attribute)
7280
return objects

0 commit comments

Comments
 (0)