From 3d3d4d8109d8a38550b32efdb0170fcf2ad8b515 Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 03:35:04 +0000 Subject: [PATCH 01/13] [Sync Iteration] python/wordy/1 --- solutions/python/wordy/1/wordy.py | 89 +++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 solutions/python/wordy/1/wordy.py diff --git a/solutions/python/wordy/1/wordy.py b/solutions/python/wordy/1/wordy.py new file mode 100644 index 0000000..8673c38 --- /dev/null +++ b/solutions/python/wordy/1/wordy.py @@ -0,0 +1,89 @@ +""" +Parse and evaluate simple math word problems +returning the answer as an integer. + +Handle a set of operations, in sequence. + +Since these are verbal word problems, evaluate +the expression from left-to-right, ignoring the +typical order of operations. +""" + +import string + +STR_TO_OPERATOR: dict = { + "plus": "+", + "minus": "-", + "multiplied": "*", + "divided": "/", +} + +WRONG_OPERATORS: list[str] = [ + "plus?", + "minus?", + "multiplied?", + "divided?", + "plus plus", + "plus multiplied", + "minus minus", + "multiplied multiplied", + "divided divided", + "What is?", +] + + +def answer(question: str) -> int: + if "cubed" in question: + raise ValueError("unknown operation") + + for item in WRONG_OPERATORS: + if item in question: + raise ValueError("syntax error") + + digits: list[bool] = [] + for char in question: + if char.isdigit(): + digits.append(True) + + operators: list[bool] = [] + for key, val in STR_TO_OPERATOR.items(): + if key in question or val in question: + operators.append(True) + + if not any(digits + operators): + raise ValueError("unknown operation") + + print(f"\n{question}") + return eval(_reformat(question)) + + +def _reformat(question: str) -> str: + # 1 + question = question.replace("?", "") + # 2 + chars: list[str] = list(char for char in question) + for i, char in enumerate(chars): + if char in STR_TO_OPERATOR.values(): + chars[i] = f" {char} " + # 3 + question = "".join(chars) + print(f"\n new question:{question}") + # 4 + question_list: list[str] = question.split() + for i, item in enumerate(question_list): + # print(f"\nitem: {item}") + if not ( + item.isdigit() + or item in STR_TO_OPERATOR.keys() + or item in STR_TO_OPERATOR.values() + ): + question_list[i] = "" + # print("\n#1") + elif item in STR_TO_OPERATOR.keys(): + # print("\n#2") + question_list[i] = STR_TO_OPERATOR[item] + + print(f"\n{question_list}") + print(f"\n{' '.join(question_list)}") + + return " ".join(question_list) From 38dfe1c9e962812154bec5756d738e4fd22491db Mon Sep 17 00:00:00 2001 From: "exercism-solutions-syncer[bot]" <211797793+exercism-solutions-syncer[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 04:37:40 +0000 Subject: [PATCH 02/13] [Sync Iteration] python/wordy/2 --- solutions/python/wordy/2/wordy.py | 118 ++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 solutions/python/wordy/2/wordy.py diff --git a/solutions/python/wordy/2/wordy.py b/solutions/python/wordy/2/wordy.py new file mode 100644 index 0000000..09feb08 --- /dev/null +++ b/solutions/python/wordy/2/wordy.py @@ -0,0 +1,118 @@ +""" +Parse and evaluate simple math word problems +returning the answer as an integer. + +Handle a set of operations, in sequence. + +Since these are verbal word problems, evaluate +the expression from left-to-right, ignoring the +typical order of operations. +""" + +import string + +STR_TO_OPERATOR: dict = { + "plus": "+", + "minus": "-", + "multiplied": "*", + "divided": "/", +} + +WRONG_OPERATORS: list[str] = [ + "plus?", + "minus?", + "multiplied?", + "divided?", + "plus plus", + "plus multiplied", + "minus multiplied", + "minus minus", + "multiplied multiplied", + "divided divided", + "What is?", +] + + +def answer(question: str) -> int: + _validate_errors(question) + result: int = 0 + new_question: list[str] = _reformat(question) + + if len(new_question) <= 3: + try: + _validate_evaluation_pattern(new_question) + eval_str: str = "".join(new_question) + result = eval(eval_str) + return result + except Exception: + raise ValueError("syntax error") + + while len(new_question) >= 3: + try: + _validate_evaluation_pattern(new_question[:3]) + eval_str: str = "".join(new_question[:3]) + val: int = eval(eval_str) + result = val + new_question = [str(result)] + new_question[3:] + if len(new_question) < 3: + _validate_evaluation_pattern(new_question) + return eval("".join(new_question)) + except Exception: + raise ValueError("syntax error") + return result + + +def _validate_evaluation_pattern(val: list) -> None: + if len(val) == 3 and not val[1] in STR_TO_OPERATOR.values(): + raise ValueError("syntax error") + + if len(val) == 2 and not val[0] in STR_TO_OPERATOR.values(): + raise ValueError("syntax error") + + +def _reformat(question: str) -> list: + # 1: Remove '?' mark + question = question.replace("?", "") + # 2: Convert all operators writen in word into proper math sign + question_list: list[str] = question.split() + formated_question_list: list[str] = [] + for i, item in enumerate(question_list): + if not ( + item.isdigit() + or item in STR_TO_OPERATOR.keys() + or item in STR_TO_OPERATOR.values() + or any(val in item for val in STR_TO_OPERATOR.values()) + ): + continue + elif item in STR_TO_OPERATOR.keys(): + formated_question_list.append(STR_TO_OPERATOR[item]) + elif item.isdigit(): + formated_question_list.append(item) + elif item in STR_TO_OPERATOR.values(): + formated_question_list.append(item) + elif any(val in item for val in STR_TO_OPERATOR.values()): + formated_question_list.append(item) + + return formated_question_list + + +def _validate_errors(question: str) -> None: + if "cubed" in question: + raise ValueError("unknown operation") + + for item in WRONG_OPERATORS: + if item in question: + raise ValueError("syntax error") + + digits: list[bool] = [] + for char in question: + if char.isdigit(): + digits.append(True) + + operators: list[bool] = [] + for key, val in STR_TO_OPERATOR.items(): + if key in question or val in question: + operators.append(True) + + if not any(digits + operators): + raise ValueError("unknown operation") From c8efcfac7833de2707d26518fc54a6369618b2a1 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 11:45:34 -0700 Subject: [PATCH 03/13] Improved wordy --- wordy/wordy.py | 66 +++++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 30 deletions(-) diff --git a/wordy/wordy.py b/wordy/wordy.py index 12a6fcb..ac8ee06 100644 --- a/wordy/wordy.py +++ b/wordy/wordy.py @@ -44,30 +44,49 @@ def answer(question: str) -> int: _validate_errors(question) result: int = 0 new_question: list[str] = _reformat(question) - - if len(new_question) <= 3: - try: - _validate_evaluation_pattern(new_question) - eval_str: str = "".join(new_question) - result = eval(eval_str) - return result - except Exception as exc: - raise ValueError("syntax error") from exc - # Reduce iteratively: evaluate the first three-token slice # and fold the result left-to-right. - while len(new_question) >= 3: + while new_question: try: + if len(new_question) == 3: + _validate_evaluation_pattern(new_question) + return _math_operation(new_question) + + if len(new_question) == 1: + return int(new_question[0]) + _validate_evaluation_pattern(new_question[:3]) - eval_str: str = "".join(new_question[:3]) - val: int = eval(eval_str) - result = val + result = _math_operation(new_question[:3]) new_question = [str(result)] + new_question[3:] - if len(new_question) < 3: - _validate_evaluation_pattern(new_question) - return eval("".join(new_question)) except Exception as exc: raise ValueError("syntax error") from exc + + +def _math_operation(question: list[str]) -> int: + """ + Compute a single binary arithmetic operation. + + Expects a three-token slice like ``['3', '+', '4']`` and returns + the integer result. Division performs floor division (``//``) to + match exercise rules. + + :param question: Three tokens ``[lhs, operator, rhs]``. + :type question: list[str] + :returns: The computed integer result. + :rtype: int + """ + math_operator: str = question[1] + result: int = 0 + + if math_operator == "+": + result = int(question[0]) + int(question[-1]) + elif math_operator == "-": + result = int(question[0]) - int(question[-1]) + elif math_operator == "/": + result = int(question[0]) // int(question[-1]) + elif math_operator == "*": + result = int(question[0]) * int(question[-1]) + return result @@ -138,16 +157,3 @@ def _validate_errors(question: str) -> None: for item in WRONG_OPERATORS: if item in question: raise ValueError("syntax error") - - digits: list[bool] = [] - for char in question: - if char.isdigit(): - digits.append(True) - - operators: list[bool] = [] - for key, val in STR_TO_OPERATOR.items(): - if key in question or val in question: - operators.append(True) - - if not any(digits + operators): - raise ValueError("unknown operation") From 6a010e6986eb2916dafb0f4035f428c3196afbce Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:05:59 -0700 Subject: [PATCH 04/13] Reusable workflows --- .github/workflows/codecov.yml | 33 ++++++++++++----------- .github/workflows/flake8.yml | 12 +++------ .github/workflows/lint_test_report.yml | 37 ++++++++++++++++++++++++++ .github/workflows/pylint.yml | 12 +++------ .github/workflows/pytest.yml | 10 ++++--- .github/workflows/ruff.yml | 13 +++------ wordy/wordy.py | 10 ++++--- 7 files changed, 77 insertions(+), 50 deletions(-) create mode 100644 .github/workflows/lint_test_report.yml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 73a39b0..682a973 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -1,11 +1,13 @@ --- -name: Codecov Coverage Report +name: "Codecov Coverage Report" on: # yamllint disable-line rule:truthy - push: - branches: [ main ] - pull_request: - branches: [ main ] + # Makes it reusable; called by other workflows + workflow_call: + secrets: + CODECOV_TOKEN: + description: "CODECOV Auth Token" + required: true jobs: run: @@ -15,27 +17,28 @@ jobs: timeout-minutes: 10 strategy: matrix: - os: [ubuntu-latest] + os: ["ubuntu-latest"] python-version: ["3.12"] steps: - - uses: actions/checkout@main - - name: Setup Python - uses: actions/setup-python@main + - uses: "actions/checkout@main" + - name: "Setup Python" + uses: "actions/setup-python@main" with: python-version: ${{ matrix.python-version }} - name: "Cache PIP Dependencies" uses: "actions/cache@v4" with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} + key: | + ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- - - name: Install prerequisites + - name: "Install prerequisites" run: | python -m pip install --upgrade pip setuptools wheel pip install -r requirements.txt - - name: Install pytest, pytest-cov + - name: "Install pytest, pytest-cov" run: | pip install pytest pip install pytest-cov @@ -44,12 +47,12 @@ jobs: run: | python -c "import os; print(os.getcwd())" python -m pytest . --ignore=solutions --log-cli-level=INFO -v --cov-report term-missing --cov --cov-branch --cov-report=xml - - name: List test files + - name: "List test files" run: | ls *.xml # yamllint enable rule:line-length - - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.5.1 + - name: "Upload coverage to Codecov" + uses: "codecov/codecov-action@v5.5.1" with: token: ${{ secrets.CODECOV_TOKEN }} files: coverage.xml diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index bd102b3..d89aba0 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -1,15 +1,9 @@ --- -name: "Flake8" +name: "Flake8 Workflow" on: # yamllint disable-line rule:truthy - push: - branches: [ main ] - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' - pull_request: - branches: [ main ] + # Makes it reusable; called by other workflows + workflow_call: paths: - '**/*.py' - 'requirements.txt' diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml new file mode 100644 index 0000000..794f130 --- /dev/null +++ b/.github/workflows/lint_test_report.yml @@ -0,0 +1,37 @@ +--- +name: "Lint->Test->Report" + +on: # yamllint disable-line rule:truthy + push: + branches: [ main ] + pull_request: + branches: [ main ] + +permissions: + contents: "read" + pull-requests: "read" + +jobs: + ruff: + name: "Ruff Lint and Format" + uses: "./.github/workflows/ruff.yml" + pylint: + name: "Pylint Workflow" + uses: "./.github/workflows/pylint.yml" + flake8: + name: "Flake8 Workflow" + uses: "./.github/workflows/flake8.yml" + pytest: + name: "Pytest Workflow" + needs: + - "ruff" + - "pylint" + - "flake8" + uses: "./.github/workflows/pytest.yml" + codecov: + name: "Codecov Coverage Report" + needs: + - "pytest" + uses: "./.github/workflows/codecov.yml" + secrets: + CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" \ No newline at end of file diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 0ae436f..7d35e25 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,15 +1,9 @@ --- -name: "Pylint" +name: "Pylint Workflow" on: - push: - branches: [ main ] - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' - pull_request: - branches: [ main ] + # Makes it reusable; called by other workflows + workflow_call: paths: - '**/*.py' - 'requirements.txt' diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index fb740ab..a4d5b3c 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -2,10 +2,12 @@ name: "Pytest Workflow" on: - push: - branches: [ main ] - pull_request: - branches: [ main ] + # Makes it reusable; called by other workflows + workflow_call: + paths: + - '**/*.py' + - 'requirements.txt' + - '.pylintrc' jobs: test: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 7666f2e..e0d0eb1 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -2,14 +2,8 @@ name: "Ruff Lint and Format" on: - push: - branches: [ main ] - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' - pull_request: - branches: [ main ] + # Makes it reusable; called by other workflows + workflow_call: paths: - '**/*.py' - 'requirements.txt' @@ -31,7 +25,8 @@ jobs: uses: "actions/cache@v4" with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} + key: | + ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- diff --git a/wordy/wordy.py b/wordy/wordy.py index ac8ee06..da43570 100644 --- a/wordy/wordy.py +++ b/wordy/wordy.py @@ -46,20 +46,22 @@ def answer(question: str) -> int: new_question: list[str] = _reformat(question) # Reduce iteratively: evaluate the first three-token slice # and fold the result left-to-right. + result: int = 0 while new_question: try: if len(new_question) == 3: _validate_evaluation_pattern(new_question) - return _math_operation(new_question) - + result = _math_operation(new_question) + break if len(new_question) == 1: - return int(new_question[0]) - + result = int(new_question[0]) + break _validate_evaluation_pattern(new_question[:3]) result = _math_operation(new_question[:3]) new_question = [str(result)] + new_question[3:] except Exception as exc: raise ValueError("syntax error") from exc + return result def _math_operation(question: list[str]) -> int: From c968566d36cd785c14421453c779302e383250e5 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:10:14 -0700 Subject: [PATCH 05/13] Updated worflows --- .github/workflows/flake8.yml | 4 ---- .github/workflows/lint_test_report.yml | 8 ++++++++ .github/workflows/pylint.yml | 4 ---- .github/workflows/pytest.yml | 4 ---- .github/workflows/ruff.yml | 4 ---- 5 files changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index d89aba0..4d42568 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -4,10 +4,6 @@ name: "Flake8 Workflow" on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' permissions: contents: "read" diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml index 794f130..8a7cd2a 100644 --- a/.github/workflows/lint_test_report.yml +++ b/.github/workflows/lint_test_report.yml @@ -4,8 +4,16 @@ name: "Lint->Test->Report" on: # yamllint disable-line rule:truthy push: branches: [ main ] + paths: + - '**/*.py' + - 'requirements.txt' + - '.pylintrc' pull_request: branches: [ main ] + paths: + - '**/*.py' + - 'requirements.txt' + - '.pylintrc' permissions: contents: "read" diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 7d35e25..d789862 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -4,10 +4,6 @@ name: "Pylint Workflow" on: # Makes it reusable; called by other workflows workflow_call: - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' jobs: build: diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a4d5b3c..a8ddfae 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,10 +4,6 @@ name: "Pytest Workflow" on: # Makes it reusable; called by other workflows workflow_call: - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' jobs: test: diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index e0d0eb1..4c443c3 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -4,10 +4,6 @@ name: "Ruff Lint and Format" on: # Makes it reusable; called by other workflows workflow_call: - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' jobs: ruff: From 28339cad6d5eb4984e918fcb9da483e162de125c Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:11:23 -0700 Subject: [PATCH 06/13] Update lint_test_report.yml --- .github/workflows/lint_test_report.yml | 8 -------- 1 file changed, 8 deletions(-) diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml index 8a7cd2a..794f130 100644 --- a/.github/workflows/lint_test_report.yml +++ b/.github/workflows/lint_test_report.yml @@ -4,16 +4,8 @@ name: "Lint->Test->Report" on: # yamllint disable-line rule:truthy push: branches: [ main ] - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' pull_request: branches: [ main ] - paths: - - '**/*.py' - - 'requirements.txt' - - '.pylintrc' permissions: contents: "read" From 604343097771c7f7879bf9dae4bc19cd04ea1884 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:16:20 -0700 Subject: [PATCH 07/13] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index 2877080..21ae106 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # [Exercism Python Track](https://exercism.io/tracks/python) -[![Pytest Workflow](https://github.com/ikostan/python/actions/workflows/pytest.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pytest.yml) -[![Pylint](https://github.com/ikostan/python/actions/workflows/pylint.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pylint.yml) -[![Ruff Lint and Format](https://github.com/ikostan/python/actions/workflows/ruff.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/ruff.yml) -[![Flake8](https://github.com/ikostan/python/actions/workflows/flake8.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/flake8.yml) -[![codecov](https://codecov.io/github/ikostan/python/graph/badge.svg?token=G78RWJWJ38)](https://codecov.io/github/ikostan/python) +[![Main Workflow](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml)
From cbd7df99d247b20fd740a43761e1d52160980c09 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:18:28 -0700 Subject: [PATCH 08/13] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 21ae106..c90f69f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # [Exercism Python Track](https://exercism.io/tracks/python) [![Main Workflow](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml) +[![Pytest Workflow](https://github.com/ikostan/python/actions/workflows/pytest.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pytest.yml) +[![Pylint](https://github.com/ikostan/python/actions/workflows/pylint.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pylint.yml) +[![Ruff Lint and Format](https://github.com/ikostan/python/actions/workflows/ruff.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/ruff.yml) +[![Flake8](https://github.com/ikostan/python/actions/workflows/flake8.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/flake8.yml) +[![codecov](https://codecov.io/github/ikostan/python/graph/badge.svg?token=G78RWJWJ38)](https://codecov.io/github/ikostan/python)
From 80dea0997cbc6ba6aa3d96b959ebdfc8f5cde082 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:43:31 -0700 Subject: [PATCH 09/13] Update lint_test_report.yml --- .github/workflows/lint_test_report.yml | 44 ++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml index 794f130..9fa7b65 100644 --- a/.github/workflows/lint_test_report.yml +++ b/.github/workflows/lint_test_report.yml @@ -1,5 +1,5 @@ --- -name: "Lint->Test->Report" +name: "Main Pipeline" on: # yamllint disable-line rule:truthy push: @@ -12,26 +12,66 @@ permissions: pull-requests: "read" jobs: + changed-files: + runs-on: "ubuntu-latest" + outputs: + python_changed: ${{ steps.python-files.outputs.any_changed }} + yaml_changed: ${{ steps.yaml-files.outputs.any_changed }} + steps: + - uses: "actions/checkout@v5" + - name: "Get changed Python files" + id: python-files + uses: "tj-actions/changed-files@v47" + with: + files: | + **/*.py + requirements.txt + .pylintrc + - name: "Get changed YAML files" + id: yaml-files + uses: "tj-actions/changed-files@v47" + with: + files: | + **/*.yml + **/*.yaml + + yamllint: + name: "YAML Lint Workflow" + needs: changed-files + if: needs.changed-files.outputs.yaml_changed == 'true' + uses: "./.github/workflows/yamllint.yml" + ruff: name: "Ruff Lint and Format" + needs: changed-files + if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/ruff.yml" + pylint: name: "Pylint Workflow" + needs: changed-files + if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/pylint.yml" + flake8: name: "Flake8 Workflow" + needs: changed-files + if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/flake8.yml" + pytest: name: "Pytest Workflow" needs: + - "yamllint" - "ruff" - "pylint" - "flake8" uses: "./.github/workflows/pytest.yml" + codecov: name: "Codecov Coverage Report" needs: - "pytest" uses: "./.github/workflows/codecov.yml" secrets: - CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" \ No newline at end of file + CODECOV_TOKEN: "${{ secrets.CODECOV_TOKEN }}" From 6eaedd87bd96a7528049d983f426e0e3a8d228ed Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:45:30 -0700 Subject: [PATCH 10/13] Worflow update --- .github/workflows/lint_test_report.yml | 2 +- .github/workflows/yamllint.yml | 38 ++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/yamllint.yml diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml index 9fa7b65..69ed373 100644 --- a/.github/workflows/lint_test_report.yml +++ b/.github/workflows/lint_test_report.yml @@ -1,7 +1,7 @@ --- name: "Main Pipeline" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy push: branches: [ main ] pull_request: diff --git a/.github/workflows/yamllint.yml b/.github/workflows/yamllint.yml new file mode 100644 index 0000000..1d797de --- /dev/null +++ b/.github/workflows/yamllint.yml @@ -0,0 +1,38 @@ +--- +name: "YAML Lint Workflow" + +on: # yamllint disable-line rule:truthy + # Makes it reusable; called by other workflows + workflow_call: + +jobs: + yamllint: + runs-on: "ubuntu-latest" + # Adding 'timeout-minutes: 10' would prevent jobs from running + # indefinitely if something goes wrong + timeout-minutes: 10 + steps: + - uses: "actions/checkout@v5" + - name: "Set up Python 3.12" + uses: "actions/setup-python@v6" + with: + python-version: "3.12" + - name: "Cache PIP Dependencies" + uses: "actions/cache@v4" + with: + path: ~/.cache/pip + key: | + ${{ runner.os }}-pip-3.12-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip-3.12- + ${{ runner.os }}-pip- + - name: "Install yamllint" + run: | + python -m pip install --upgrade pip + pip install yamllint + - name: "Check yamllint Version" + run: | + yamllint --version + - name: "Lint with yamllint" + run: | + yamllint . From 5069b8210bf885c3f6a0752c38fbff508411fa89 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:47:19 -0700 Subject: [PATCH 11/13] Update README.md --- README.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/README.md b/README.md index c90f69f..c3cd5c0 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,6 @@ # [Exercism Python Track](https://exercism.io/tracks/python) -[![Main Workflow](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml) -[![Pytest Workflow](https://github.com/ikostan/python/actions/workflows/pytest.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pytest.yml) -[![Pylint](https://github.com/ikostan/python/actions/workflows/pylint.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/pylint.yml) -[![Ruff Lint and Format](https://github.com/ikostan/python/actions/workflows/ruff.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/ruff.yml) -[![Flake8](https://github.com/ikostan/python/actions/workflows/flake8.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/flake8.yml) +[![Main Pipeline](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml/badge.svg)](https://github.com/ikostan/python/actions/workflows/lint_test_report.yml) [![codecov](https://codecov.io/github/ikostan/python/graph/badge.svg?token=G78RWJWJ38)](https://codecov.io/github/ikostan/python)
From 5ab8f6b39e102d14be1eaf8f420f14366612c39b Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 12:50:11 -0700 Subject: [PATCH 12/13] Update lint_test_report.yml --- .github/workflows/lint_test_report.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint_test_report.yml b/.github/workflows/lint_test_report.yml index 69ed373..8e0813c 100644 --- a/.github/workflows/lint_test_report.yml +++ b/.github/workflows/lint_test_report.yml @@ -37,32 +37,38 @@ jobs: yamllint: name: "YAML Lint Workflow" - needs: changed-files + needs: + - "changed-files" if: needs.changed-files.outputs.yaml_changed == 'true' uses: "./.github/workflows/yamllint.yml" ruff: name: "Ruff Lint and Format" - needs: changed-files + needs: + - "changed-files" + - "yamllint" if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/ruff.yml" pylint: name: "Pylint Workflow" - needs: changed-files + needs: + - "changed-files" + - "yamllint" if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/pylint.yml" flake8: name: "Flake8 Workflow" - needs: changed-files + needs: + - "changed-files" + - "yamllint" if: needs.changed-files.outputs.python_changed == 'true' uses: "./.github/workflows/flake8.yml" pytest: name: "Pytest Workflow" needs: - - "yamllint" - "ruff" - "pylint" - "flake8" From 7a543d51222aa4fc07522eb048873c824625b300 Mon Sep 17 00:00:00 2001 From: Egor Kostan Date: Tue, 30 Sep 2025 13:03:37 -0700 Subject: [PATCH 13/13] Test workflow scheme --- .github/workflows/codecov.yml | 2 +- .github/workflows/flake8.yml | 10 +++++--- .github/workflows/pylint.yml | 46 ++++++++++++++++----------------- .github/workflows/pytest.yml | 48 +++++++++++++++++------------------ .github/workflows/ruff.yml | 4 +-- .yamllint.yml | 3 +++ Dockerfile | 3 ++- codecov.yml | 5 ++-- run_ci_tests.sh | 4 +++ 9 files changed, 68 insertions(+), 57 deletions(-) create mode 100644 .yamllint.yml diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 682a973..e6590f5 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -1,7 +1,7 @@ --- name: "Codecov Coverage Report" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: secrets: diff --git a/.github/workflows/flake8.yml b/.github/workflows/flake8.yml index 4d42568..176cefb 100644 --- a/.github/workflows/flake8.yml +++ b/.github/workflows/flake8.yml @@ -1,7 +1,7 @@ --- name: "Flake8 Workflow" -on: # yamllint disable-line rule:truthy +on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: @@ -23,21 +23,23 @@ jobs: - name: Set up Python ${{ matrix.python-version }} # This is the version of the action for setting up Python, # not the Python version. - uses: actions/setup-python@v6 + uses: "actions/setup-python@v6" with: python-version: ${{ matrix.python-version }} - name: "Cache PIP Dependencies" uses: "actions/cache@v4" with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} + key: | + ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ matrix.python-version }}- ${{ runner.os }}-pip- # You can test your matrix by printing the current # Python version - name: "Display Python Version" - run: python -c "import sys; print(sys.version)" + run: | + python -c "import sys; print(sys.version)" - name: "Install Dependencies" run: | python -m pip install --upgrade pip diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index d789862..ff25eb1 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,7 +1,7 @@ --- name: "Pylint Workflow" -on: +on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: @@ -15,25 +15,25 @@ jobs: matrix: python-version: ["3.12"] steps: - - uses: "actions/checkout@v5" - - name: Set up Python ${{ matrix.python-version }} - uses: "actions/setup-python@v6" - with: - python-version: ${{ matrix.python-version }} - - name: "Cache PIP Dependencies" - uses: "actions/cache@v4" - with: - path: ~/.cache/pip - key: | - ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - ${{ runner.os }}-pip- - - name: "Install dependencies" - run: | - python -m pip install --upgrade pip - pip install pylint - pip install -r requirements.txt - - name: "Analysing the code with pylint" - run: | - python -m pylint --verbose $(find . -name "*.py" ! -path "*/.venv/*" ! -path "*/venv/*") --rcfile=.pylintrc \ No newline at end of file + - uses: "actions/checkout@v5" + - name: Set up Python ${{ matrix.python-version }} + uses: "actions/setup-python@v6" + with: + python-version: ${{ matrix.python-version }} + - name: "Cache PIP Dependencies" + uses: "actions/cache@v4" + with: + path: ~/.cache/pip + key: | + ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip + pip install pylint + pip install -r requirements.txt + - name: "Analysing the code with pylint" + run: | + python -m pylint --verbose $(find . -name "*.py" ! -path "*/.venv/*" ! -path "*/venv/*") --rcfile=.pylintrc diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index a8ddfae..7737a16 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,7 +1,7 @@ --- name: "Pytest Workflow" -on: +on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: @@ -12,26 +12,26 @@ jobs: # indefinitely if something goes wrong timeout-minutes: 10 steps: - - name: "Checkout code" - uses: "actions/checkout@v5" - - name: "Set up Python 3.12" - uses: "actions/setup-python@v6" - with: - python-version: "3.12" - - name: "Cache PIP Dependencies" - uses: "actions/cache@v4" - with: - path: ~/.cache/pip - key: | - ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} - restore-keys: | - ${{ runner.os }}-pip-${{ matrix.python-version }}- - ${{ runner.os }}-pip- - - name: "Install dependencies" - run: | - python -m pip install --upgrade pip - pip install -r requirements.txt - pip install pytest - - name: "Run pytest" - run: | - pytest . --verbose --ignore=solutions --log-cli-level=INFO \ No newline at end of file + - name: "Checkout code" + uses: "actions/checkout@v5" + - name: "Set up Python 3.12" + uses: "actions/setup-python@v6" + with: + python-version: "3.12" + - name: "Cache PIP Dependencies" + uses: "actions/cache@v4" + with: + path: ~/.cache/pip + key: | + ${{ runner.os }}-pip-${{ matrix.python-version }}-${{ hashFiles('requirements.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ matrix.python-version }}- + ${{ runner.os }}-pip- + - name: "Install dependencies" + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest + - name: "Run pytest" + run: | + pytest . --verbose --ignore=solutions --log-cli-level=INFO diff --git a/.github/workflows/ruff.yml b/.github/workflows/ruff.yml index 4c443c3..7e4659c 100644 --- a/.github/workflows/ruff.yml +++ b/.github/workflows/ruff.yml @@ -1,7 +1,7 @@ --- name: "Ruff Lint and Format" -on: +on: # yamllint disable-line rule:truthy # Makes it reusable; called by other workflows workflow_call: @@ -35,4 +35,4 @@ jobs: ruff check --output-format=github . - name: "Run Ruff format check" run: | - ruff format --check . \ No newline at end of file + ruff format --check . diff --git a/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..0c01e2b --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,3 @@ +--- +rules: + line-length: disable diff --git a/Dockerfile b/Dockerfile index 8a235c6..54894c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -38,7 +38,8 @@ RUN pip install --no-cache-dir \ flake8 \ pylint \ pytest \ - pytest-cov + pytest-cov \ + yamllint # Copy the rest of the code (including .pylintrc if present) COPY . . diff --git a/codecov.yml b/codecov.yml index ff4805c..63b1b89 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,5 @@ +--- ignore: - "**/tests/" # Ignores all files within any 'tests' directory - - "**/*_test.py" # Ignores files ending with '_test.py' - - "test_*.py" # Ignores files starting with 'test_' \ No newline at end of file + - "**/*_test.py" # Ignores files ending with '_test.py' + - "test_*.py" # Ignores files starting with 'test_' diff --git a/run_ci_tests.sh b/run_ci_tests.sh index 716bb0c..3796ace 100644 --- a/run_ci_tests.sh +++ b/run_ci_tests.sh @@ -6,6 +6,10 @@ set -e # Display Python version python --version +# Run YAML lint +echo "Running YAML lint..." +yamllint . || { echo "YAML lint failed"; exit 1; } + # Run Ruff lint echo "Running Ruff lint..." ruff check --output-format=github . || { echo "Ruff lint failed"; exit 1; }