From 8de9d8919d8cf920fc43184c024dde5dad13be5a Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 5 Aug 2025 15:21:44 +0300 Subject: [PATCH 01/86] test case --- tests/typing/decorators/test_superfunction_typing.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/typing/decorators/test_superfunction_typing.py b/tests/typing/decorators/test_superfunction_typing.py index 65db39f..84cb2ae 100644 --- a/tests/typing/decorators/test_superfunction_typing.py +++ b/tests/typing/decorators/test_superfunction_typing.py @@ -16,6 +16,7 @@ Что нужно проверить: 1. Что await_it, yield_from_it и yield_it типизированы. +2. Нельзя сохранять возвращаемое значение суперфункции в переменную. Что проверено: From 0263fdaea10e05cd6d505723504a54108ea9acb1 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 5 Aug 2025 17:05:19 +0300 Subject: [PATCH 02/86] readme --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index e365f0c..cb42046 100644 --- a/README.md +++ b/README.md @@ -297,3 +297,15 @@ However, it is not completely free. The fact is that this mode uses a special tr - Exceptions will not work normally inside this function. Rather, they can be picked up and intercepted in [`sys.unraisablehook`](https://docs.python.org/3/library/sys.html#sys.unraisablehook), but they will not go up the stack above this function. This is due to a feature of CPython: exceptions that occur inside callbacks for finalizing objects are completely escaped. This mode is well suited for functions such as logging or sending statistics from your code: simple functions from which no exceptions or return values are expected. In all other cases, I recommend using the tilde syntax. + + +## Typing + +Typing is the most difficult problem we faced when developing this library. In most situations, it has already been solved, but in some cases you may still notice flaws when using `mypy` or other static type analyzers. If you encounter similar problems, please [report](https://github.com/pomponchik/transfunctions/issues) them. + +There are 2 main difficulties in developing typing here: + +- Code generation creates code in runtime that is not in the source files of your project. Whereas most type analyzers look at your code statically, at what is actually present in your files. +- We mix several types of syntax in a single template function, but the static analyzer does not know that this is a template and part of the code will be deleted from here. In its opinion, this is the final function that will continue to be used in your project. + +As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly remove code from functions, but hardly add it there during code generation. In other words, we almost never encounter the problem of how to type the added code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. From 90b7c83b90adde2554487a81231c26eaec469816 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 5 Aug 2025 17:06:43 +0300 Subject: [PATCH 03/86] readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cb42046..e29e6b3 100644 --- a/README.md +++ b/README.md @@ -308,4 +308,4 @@ There are 2 main difficulties in developing typing here: - Code generation creates code in runtime that is not in the source files of your project. Whereas most type analyzers look at your code statically, at what is actually present in your files. - We mix several types of syntax in a single template function, but the static analyzer does not know that this is a template and part of the code will be deleted from here. In its opinion, this is the final function that will continue to be used in your project. -As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly remove code from functions, but hardly add it there during code generation. In other words, we almost never encounter the problem of how to type the added code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. +As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly remove code from functions, but hardly add it there during code generation. In other words, we almost never encounter the problem of how to type the *added* code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. From 1d1bd2bf9b66947b2952b9cf2726ee8151951ed2 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 5 Aug 2025 17:07:46 +0300 Subject: [PATCH 04/86] readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e29e6b3..9a1a533 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ This library is designed to solve one of the most important problems in python p - [**Code generation**](#code-generation) - [**Markers**](#markers) - [**Superfunctions**](#superfunctions) +- [**Typing**](#typing) ## Quick start From c44e18a09063426f62527193f0f7754b419055bc Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 7 Aug 2025 14:13:47 +0300 Subject: [PATCH 05/86] comment the xfail flag --- tests/typing/decorators/test_superfunction_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/typing/decorators/test_superfunction_typing.py b/tests/typing/decorators/test_superfunction_typing.py index 84cb2ae..1491ae3 100644 --- a/tests/typing/decorators/test_superfunction_typing.py +++ b/tests/typing/decorators/test_superfunction_typing.py @@ -217,7 +217,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # TODO: we should understand why it works -@pytest.mark.xfail +#@pytest.mark.xfail @pytest.mark.mypy_testing def test_wrong_using_of_generator_function_with_simple_yield_from() -> None: @superfunction From 26080f2b8fd35135f6c608cbb143b8b584a78a78 Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 7 Aug 2025 14:28:52 +0300 Subject: [PATCH 06/86] uncomment the xfail flag --- tests/typing/decorators/test_superfunction_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/typing/decorators/test_superfunction_typing.py b/tests/typing/decorators/test_superfunction_typing.py index 1491ae3..84cb2ae 100644 --- a/tests/typing/decorators/test_superfunction_typing.py +++ b/tests/typing/decorators/test_superfunction_typing.py @@ -217,7 +217,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # TODO: we should understand why it works -#@pytest.mark.xfail +@pytest.mark.xfail @pytest.mark.mypy_testing def test_wrong_using_of_generator_function_with_simple_yield_from() -> None: @superfunction From 2f551f752281f24b16ae23f118d1714b457af9f4 Mon Sep 17 00:00:00 2001 From: esblinov Date: Fri, 8 Aug 2025 19:12:11 +0300 Subject: [PATCH 07/86] add dependencies cache for the tests CI workflow --- .github/workflows/tests_and_coverage.yml | 77 +++++++++++++----------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index 48e1774..b3a7066 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -1,49 +1,54 @@ name: Tests -on: - push +on: push jobs: build: - runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: python-version: ${{ matrix.python-version }} - - name: Install the library - shell: bash - run: pip install . - - - name: Install dependencies - shell: bash - run: pip install -r requirements_dev.txt - - - name: Print all libs - shell: bash - run: pip list - - - name: Run tests and show coverage on the command line - run: | - coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 - coverage xml - - - name: Upload coverage to Coveralls - if: runner.os == 'Linux' - env: - COVERALLS_REPO_TOKEN: ${{secrets.COVERALLS_REPO_TOKEN}} - uses: coverallsapp/github-action@v2 - with: - format: cobertura - file: coverage.xml - - - name: Run tests and show the branch coverage on the command line - run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 + - name: Install the library + shell: bash + run: pip install . + + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ hashFiles('requirements_dev.txt') }} + restore-keys: | + ${{ runner.os }}-pip- + - name: Install dependencies + shell: bash + run: pip install -r requirements_dev.txt + + - name: Print all libs + shell: bash + run: pip list + + - name: Run tests and show coverage on the command line + run: | + coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 + coverage xml + + - name: Upload coverage to Coveralls + if: runner.os == 'Linux' + env: + COVERALLS_REPO_TOKEN: ${{secrets.COVERALLS_REPO_TOKEN}} + uses: coverallsapp/github-action@v2 + with: + format: cobertura + file: coverage.xml + + - name: Run tests and show the branch coverage on the command line + run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 From 783e3638212c2906c31eca56f09e052bc63243bd Mon Sep 17 00:00:00 2001 From: esblinov Date: Fri, 8 Aug 2025 19:18:01 +0300 Subject: [PATCH 08/86] up coverage --- .github/workflows/tests_and_coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index b3a7066..b1c75e9 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -38,7 +38,7 @@ jobs: - name: Run tests and show coverage on the command line run: | - coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 + coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=95 coverage xml - name: Upload coverage to Coveralls @@ -51,4 +51,4 @@ jobs: file: coverage.xml - name: Run tests and show the branch coverage on the command line - run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=90 + run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=95 From 35db597590c42901c9efb341a36f1614bc312206 Mon Sep 17 00:00:00 2001 From: esblinov Date: Fri, 8 Aug 2025 19:21:27 +0300 Subject: [PATCH 09/86] get coverage down --- .github/workflows/tests_and_coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index b1c75e9..927bc00 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -38,7 +38,7 @@ jobs: - name: Run tests and show coverage on the command line run: | - coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=95 + coverage run --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=94 coverage xml - name: Upload coverage to Coveralls @@ -51,4 +51,4 @@ jobs: file: coverage.xml - name: Run tests and show the branch coverage on the command line - run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=95 + run: coverage run --branch --source=transfunctions --omit="*tests*" -m pytest --cache-clear --assert=plain && coverage report -m --fail-under=94 From ce6f89746553e37c8309f24d8dbda37b17f87303 Mon Sep 17 00:00:00 2001 From: esblinov Date: Fri, 8 Aug 2025 19:31:49 +0300 Subject: [PATCH 10/86] empty line --- .github/workflows/tests_and_coverage.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index 927bc00..fdb1cdc 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -28,6 +28,7 @@ jobs: key: ${{ runner.os }}-pip-${{ hashFiles('requirements_dev.txt') }} restore-keys: | ${{ runner.os }}-pip- + - name: Install dependencies shell: bash run: pip install -r requirements_dev.txt From 8ae5f47e54dc8772c152adc5b4613b77feb5e035 Mon Sep 17 00:00:00 2001 From: esblinov Date: Sat, 9 Aug 2025 00:25:03 +0300 Subject: [PATCH 11/86] Add workflow name to pip cache key --- .github/workflows/tests_and_coverage.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index fdb1cdc..817b5b3 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -25,9 +25,9 @@ jobs: uses: actions/cache@v3 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('requirements_dev.txt') }} + key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }} restore-keys: | - ${{ runner.os }}-pip- + ${{ runner.os }}-pip-${{ github.workflow }}- - name: Install dependencies shell: bash From 1c41335774d540e32e88edee037028b49737b62a Mon Sep 17 00:00:00 2001 From: esblinov Date: Sun, 10 Aug 2025 15:33:54 +0300 Subject: [PATCH 12/86] Add pip dependency caching to the lint workflow --- .github/workflows/lint.yml | 51 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 792555a..9d016b7 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,36 +1,41 @@ name: Lint -on: - push +on: push jobs: build: - runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13'] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v3 + with: python-version: ${{ matrix.python-version }} - - name: Install dependencies - shell: bash - run: pip install -r requirements_dev.txt - - - name: Install the library - shell: bash - run: pip install . - - - name: Run ruff - shell: bash - run: ruff check transfunctions - - - name: Run ruff for tests - shell: bash - run: ruff check tests + - name: Cache pip dependencies + uses: actions/cache@v3 + with: + path: ~/.cache/pip + key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }} + restore-keys: | + ${{ runner.os }}-pip-${{ github.workflow }}- + - name: Install dependencies + shell: bash + run: pip install -r requirements_dev.txt + + - name: Install the library + shell: bash + run: pip install . + + - name: Run ruff + shell: bash + run: ruff check transfunctions + + - name: Run ruff for tests + shell: bash + run: ruff check tests From 2d6007f90b3af03d947cb2a0feb2a1c1cd218573 Mon Sep 17 00:00:00 2001 From: esblinov Date: Sun, 10 Aug 2025 20:06:39 +0300 Subject: [PATCH 13/86] empty line --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 9d016b7..2b9d6b2 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -24,6 +24,7 @@ jobs: key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }} restore-keys: | ${{ runner.os }}-pip-${{ github.workflow }}- + - name: Install dependencies shell: bash run: pip install -r requirements_dev.txt From 3405dbaa4440a06f77ec23b8014ed976f97f28f1 Mon Sep 17 00:00:00 2001 From: esblinov Date: Sun, 10 Aug 2025 22:36:11 +0300 Subject: [PATCH 14/86] gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ab45b6d..a21fce1 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,5 @@ uv.lock .history .vscode/ .idea/ -temp* \ No newline at end of file +temp* +.ropeproject From 4ee6dff3d0d9756657c58cb97953af4f7a82c170 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:02:20 +0300 Subject: [PATCH 15/86] some typing shit --- transfunctions/decorators/superfunction.py | 9 +++++---- transfunctions/decorators/transfunction.py | 9 ++++++++- transfunctions/transformer.py | 19 ++++++++++++------- transfunctions/typing.py | 3 ++- transfunctions/universal_namespace.py | 4 ++-- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index 5adb252..9c0d760 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -2,7 +2,8 @@ from ast import AST, NodeTransformer, Return from functools import wraps from inspect import currentframe -from typing import Any, Dict, Generic, List, Optional, Union, overload +from typing import Any, Dict, Generic, List, Optional, Union, Type, overload, cast +from types import FrameType, TracebackType from displayhooks import not_display @@ -60,7 +61,7 @@ def __invert__(self) -> ReturnType: def send(self, value: Any) -> Any: return self.coroutine.send(value) - def throw(self, exception_type: Any, value: Any = None, traceback: Any = None) -> None: # pragma: no cover + def throw(self, exception_type: Type[BaseException], value: Any = None, traceback: Optional[TracebackType] = None) -> None: # pragma: no cover pass def close(self) -> None: # pragma: no cover @@ -111,9 +112,9 @@ def superfunction( # type: ignore[misc] def decorator(function: Callable[FunctionParams, ReturnType]) -> Callable[FunctionParams, UsageTracer[FunctionParams, ReturnType]]: transformer = FunctionTransformer( function, - currentframe().f_back.f_lineno, # type: ignore[union-attr] + cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, "superfunction", - currentframe().f_back, + cast(FrameType, cast(FrameType, currentframe()).f_back), ) if not tilde_syntax: diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index c302398..1221c97 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -1,4 +1,6 @@ from inspect import currentframe +from typing import cast +from types import FrameType from transfunctions.transformer import FunctionTransformer from transfunctions.typing import Callable, FunctionParams, ReturnType @@ -7,4 +9,9 @@ def transfunction( function: Callable[FunctionParams, ReturnType], ) -> FunctionTransformer[FunctionParams, ReturnType]: - return FunctionTransformer(function, currentframe().f_back.f_lineno, "transfunction", currentframe().f_back) # type: ignore[union-attr] + return FunctionTransformer( + function, + cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, + "transfunction", + cast(FrameType, cast(FrameType, currentframe()).f_back), + ) # type: ignore[union-attr] diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index b5e9a65..50d86a2 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -7,6 +7,7 @@ Call, Constant, FunctionDef, + Module, Load, Name, NodeTransformer, @@ -23,7 +24,7 @@ from inspect import getfile, getsource, iscoroutinefunction, isfunction from sys import version_info from types import FunctionType, MethodType, FrameType -from typing import Any, Dict, Generic, List, Optional, Union, cast +from typing import Any, Dict, Generic, List, Optional, Union, Type, cast from dill.source import getsource as dill_getsource # type: ignore[import-untyped] @@ -34,7 +35,7 @@ WrongDecoratorSyntaxError, WrongMarkerSyntaxError, ) -from transfunctions.typing import Coroutine, Callable, Generator, FunctionParams, ReturnType +from transfunctions.typing import Coroutine, Callable, Generator, FunctionParams, ReturnType, SomeClassInstance from transfunctions.universal_namespace import UniversalNamespaceAroundFunction @@ -55,13 +56,17 @@ def __init__( self.decorator_lineno = decorator_lineno self.decorator_name = decorator_name self.frame = frame - self.base_object = None + self.base_object: Optional[SomeClassInstance] = None # type: ignore[valid-type] self.cache: Dict[str, Callable] = {} def __call__(self, *args: Any, **kwargs: Any) -> None: raise CallTransfunctionDirectlyError("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.") - def __get__(self, base_object, type=None): + def __get__( + self, + base_object: SomeClassInstance, + owner: Type[SomeClassInstance], + ) -> 'FunctionTransformer[FunctionParams, ReturnType]': self.base_object = base_object return self @@ -253,7 +258,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] return result - def wrap_ast_by_closures(self, tree): + def wrap_ast_by_closures(self, tree: Module) -> Module: old_functiondef = tree.body[0] tree.body[0] = FunctionDef( @@ -276,7 +281,7 @@ def wrap_ast_by_closures(self, tree): return tree - def rewrite_globals_and_closure(self, function): + def rewrite_globals_and_closure(self, function: FunctionType) -> FunctionType: # https://stackoverflow.com/a/13503277/14522393 all_new_closure_names = set(self.function.__code__.co_freevars) @@ -297,6 +302,6 @@ def rewrite_globals_and_closure(self, function): closure=filtered_closure, ) - new_function = update_wrapper(new_function, function) + new_function = cast(FunctionType, update_wrapper(new_function, function)) new_function.__kwdefaults__ = function.__kwdefaults__ return new_function diff --git a/transfunctions/typing.py b/transfunctions/typing.py index bf3fc5c..f4d9550 100644 --- a/transfunctions/typing.py +++ b/transfunctions/typing.py @@ -20,10 +20,11 @@ ReturnType = TypeVar('ReturnType') FunctionParams = ParamSpec('FunctionParams') +SomeClassInstance = TypeVar('SomeClassInstance') if sys.version_info >= (3, 9): IterableWithResults = Iterable[ReturnType] else: IterableWithResults = Iterable -__all__ = ('ParamSpec', 'TypeAlias', 'Callable', 'Coroutine', 'Generator', 'ReturnType', 'FunctionParams', 'IterableWithResults') +__all__ = ('ParamSpec', 'TypeAlias', 'Callable', 'Coroutine', 'Generator', 'ReturnType', 'FunctionParams', 'IterableWithResults', 'SomeClassInstance') diff --git a/transfunctions/universal_namespace.py b/transfunctions/universal_namespace.py index 0223fe2..63c3353 100644 --- a/transfunctions/universal_namespace.py +++ b/transfunctions/universal_namespace.py @@ -1,4 +1,4 @@ -from typing import Dict, Any +from typing import Dict, Optional, Any from types import FrameType import builtins @@ -7,7 +7,7 @@ class Nothing: pass class UniversalNamespaceAroundFunction(dict): - def __init__(self, function, frame: FrameType) -> None: + def __init__(self, function, frame: Optional[FrameType]) -> None: self.function = function self.frame = frame self.results: Dict[str, Any] = {} From 1e63db8b758b35dfdeb0f96c5fdbd1c493eeeb27 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:04:55 +0300 Subject: [PATCH 16/86] delete empty line --- transfunctions/decorators/superfunction.py | 1 - 1 file changed, 1 deletion(-) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index 9c0d760..ada0f73 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -118,7 +118,6 @@ def decorator(function: Callable[FunctionParams, ReturnType]) -> Callable[Functi ) if not tilde_syntax: - class NoReturns(NodeTransformer): def visit_Return(self, node: Return) -> Optional[Union[AST, List[AST]]]: raise WrongTransfunctionSyntaxError('A superfunction cannot contain a return statement.') From 0c979c6821ce7c6e4dd94329854f4171c72a0d44 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:13:38 +0300 Subject: [PATCH 17/86] another typing shit --- transfunctions/decorators/superfunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index ada0f73..56b6638 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -61,7 +61,7 @@ def __invert__(self) -> ReturnType: def send(self, value: Any) -> Any: return self.coroutine.send(value) - def throw(self, exception_type: Type[BaseException], value: Any = None, traceback: Optional[TracebackType] = None) -> None: # pragma: no cover + def throw(self, exception_type: Type[BaseException], value: Optional[BaseException] = None, traceback: Optional[TracebackType] = None) -> None: # type: ignore[override] # pragma: no cover pass def close(self) -> None: # pragma: no cover From 1e425a141169f73f0fa1c705d94c464007f8d7c4 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:20:44 +0300 Subject: [PATCH 18/86] another typing shit --- transfunctions/universal_namespace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/universal_namespace.py b/transfunctions/universal_namespace.py index 63c3353..cae23f8 100644 --- a/transfunctions/universal_namespace.py +++ b/transfunctions/universal_namespace.py @@ -6,7 +6,7 @@ class Nothing: pass -class UniversalNamespaceAroundFunction(dict): +class UniversalNamespaceAroundFunction(Dict[str, Any]): def __init__(self, function, frame: Optional[FrameType]) -> None: self.function = function self.frame = frame From 4e31e576b3493820790fa44c33d30d3033dd42c0 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:20:57 +0300 Subject: [PATCH 19/86] another typing shit --- transfunctions/universal_namespace.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/transfunctions/universal_namespace.py b/transfunctions/universal_namespace.py index cae23f8..fb82ea5 100644 --- a/transfunctions/universal_namespace.py +++ b/transfunctions/universal_namespace.py @@ -2,12 +2,14 @@ from types import FrameType import builtins +from transfunctions.typing import Callable, FunctionParams, ReturnType + class Nothing: pass class UniversalNamespaceAroundFunction(Dict[str, Any]): - def __init__(self, function, frame: Optional[FrameType]) -> None: + def __init__(self, function: Callable[FunctionParams, ReturnType], frame: Optional[FrameType]) -> None: self.function = function self.frame = frame self.results: Dict[str, Any] = {} From 0a1135207619fef1d76380061ffe48d630fbec75 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:25:37 +0300 Subject: [PATCH 20/86] add mypy to CI --- .github/workflows/lint.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2b9d6b2..379158d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -40,3 +40,11 @@ jobs: - name: Run ruff for tests shell: bash run: ruff check tests + + - name: Run mypy + shell: bash + run: mypy transfunctions + + - name: Run mypy for tests + shell: bash + run: mypy tests --exclude typing From 5b07682a371ee791004cf71db69e76f9b8f9f5ce Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:50:41 +0300 Subject: [PATCH 21/86] some typing shit --- transfunctions/transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 50d86a2..a5f8653 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -83,7 +83,7 @@ def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, Ret original_function = self.function class ConvertSyncFunctionToAsync(NodeTransformer): - def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]]]: + def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: if node.name == original_function.__name__: return AsyncFunctionDef( name=original_function.__name__, From cf29bf2e44fc559d4dea156f3d210da84993ad3f Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 15:53:12 +0300 Subject: [PATCH 22/86] some typing shit --- transfunctions/transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index a5f8653..6197426 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -98,7 +98,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFuncti return node class ExtractAwaitExpressions(NodeTransformer): - def visit_Call(self, node: Call) -> Optional[Union[AST, List[AST]]]: + def visit_Call(self, node: Call) -> Union[Call, Await]: if isinstance(node.func, Name) and node.func.id == 'await_it': if len(node.args) != 1 or node.keywords: raise WrongMarkerSyntaxError('The "await_it" marker can be used with only one positional argument.') From 58b6568787f29344fbc0fd8a2f8be93bd589382a Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 16:05:30 +0300 Subject: [PATCH 23/86] some typing shit --- transfunctions/transformer.py | 36 ++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 6197426..d793100 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -57,7 +57,7 @@ def __init__( self.decorator_name = decorator_name self.frame = frame self.base_object: Optional[SomeClassInstance] = None # type: ignore[valid-type] - self.cache: Dict[str, Callable] = {} + self.cache: Dict[str, Callable[FunctionParams, ReturnType]] = {} def __call__(self, *args: Any, **kwargs: Any) -> None: raise CallTransfunctionDirectlyError("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.") @@ -71,13 +71,13 @@ def __get__( return self @staticmethod - def is_lambda(function: Callable) -> bool: + def is_lambda(function: Callable[FunctionParams, ReturnType]) -> bool: # https://stackoverflow.com/a/3655857/14522393 lambda_example = lambda: 0 # noqa: E731 return isinstance(function, type(lambda_example)) and function.__name__ == lambda_example.__name__ def get_usual_function(self, addictional_transformers: Optional[List[NodeTransformer]] = None) -> Callable[FunctionParams, ReturnType]: - return self.extract_context('sync_context', addictional_transformers=addictional_transformers) + return cast(Callable[FunctionParams, ReturnType], self.extract_context('sync_context', addictional_transformers=addictional_transformers)) def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, ReturnType]]: original_function = self.function @@ -112,12 +112,15 @@ def visit_Call(self, node: Call) -> Union[Call, Await]: ) return node - return self.extract_context( - 'async_context', - addictional_transformers=[ - ConvertSyncFunctionToAsync(), - ExtractAwaitExpressions(), - ], + return cast( + Callable[FunctionParams, Coroutine[Any, Any, ReturnType]], + self.extract_context( + 'async_context', + addictional_transformers=[ + ConvertSyncFunctionToAsync(), + ExtractAwaitExpressions(), + ], + ), ) def get_generator_function(self) -> Callable[FunctionParams, Generator[ReturnType, None, None]]: @@ -136,11 +139,14 @@ def visit_Call(self, node: Call) -> Optional[Union[AST, List[AST]]]: ) return node - return self.extract_context( - 'generator_context', - addictional_transformers=[ - ConvertYieldFroms(), - ], + return cast( + Callable[FunctionParams, Generator[ReturnType, None, None]], + self.extract_context( + 'generator_context', + addictional_transformers=[ + ConvertYieldFroms(), + ], + ), ) @staticmethod @@ -159,7 +165,7 @@ def clear_spaces_from_source_code(source_code: str) -> str: return '\n'.join(new_splitted_source_code) - def extract_context(self, context_name: str, addictional_transformers: Optional[List[NodeTransformer]] = None): + def extract_context(self, context_name: str, addictional_transformers: Optional[List[NodeTransformer]] = None) -> Callable[FunctionParams, Union[Coroutine[Any, Any, ReturnType], Generator[ReturnType, None, None], ReturnType]]: if context_name in self.cache: return self.cache[context_name] try: From 268eaf4d9f1e7d761e52e4697749ac776e24fcdd Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 17:22:54 +0300 Subject: [PATCH 24/86] some typing shit --- transfunctions/decorators/transfunction.py | 2 +- transfunctions/markers.py | 2 +- transfunctions/transformer.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index 1221c97..46d4c16 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -14,4 +14,4 @@ def transfunction( cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, "transfunction", cast(FrameType, cast(FrameType, currentframe()).f_back), - ) # type: ignore[union-attr] + ) diff --git a/transfunctions/markers.py b/transfunctions/markers.py index d7ae788..0e42c81 100644 --- a/transfunctions/markers.py +++ b/transfunctions/markers.py @@ -25,6 +25,6 @@ def create_generator_context() -> Generator[NoReturn, None, None]: def await_it(some_expression: Any) -> Any: pass # pragma: no cover -def yield_from_it(some_iterable: IterableWithResults) -> NoReturn: # type: ignore[misc] +def yield_from_it(some_iterable: IterableWithResults) -> NoReturn: # type: ignore[misc, type-arg] for value in some_iterable: # pragma: no cover return value # type: ignore[misc] diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index d793100..b370b30 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -262,7 +262,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] self.cache[context_name] = result - return result + return result # type: ignore[no-any-return] def wrap_ast_by_closures(self, tree: Module) -> Module: old_functiondef = tree.body[0] From 9d5fdd0224f368a9349e6bac40677ad3dc6f4fd8 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 17:30:20 +0300 Subject: [PATCH 25/86] some typing shit --- transfunctions/transformer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index b370b30..5cc7ac3 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -85,7 +85,7 @@ def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, Ret class ConvertSyncFunctionToAsync(NodeTransformer): def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: if node.name == original_function.__name__: - return AsyncFunctionDef( + return AsyncFunctionDef( # type: ignore[no-any-return, call-overload] name=original_function.__name__, args=node.args, body=node.body, @@ -267,7 +267,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] def wrap_ast_by_closures(self, tree: Module) -> Module: old_functiondef = tree.body[0] - tree.body[0] = FunctionDef( + tree.body[0] = FunctionDef( # type: ignore[call-overload] name='wrapper', body=[Assign(targets=[Name(id=name, ctx=Store(), col_offset=0)], value=Constant(value=None, col_offset=0), col_offset=0) for name in self.function.__code__.co_freevars] + [ old_functiondef, From 9f58555de33851c167b01cb4ba09e8153007b356 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 22:56:02 +0300 Subject: [PATCH 26/86] Add `--check` flag to mypy in lint workflow --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 379158d..d899894 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - name: Run mypy shell: bash - run: mypy transfunctions + run: mypy --check transfunctions - name: Run mypy for tests shell: bash From df68f99cc04ae5537098e98532cda02ff1b59a00 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 22:57:41 +0300 Subject: [PATCH 27/86] new version tag --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ab2b6c4..54c151f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "transfunctions" -version = "0.0.8" +version = "0.0.9" authors = [ { name="Evgeniy Blinov", email="zheni-b@yandex.ru" }, ] From 672fdfe58c7c72d2a8f6064f248ede4103a8b372 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 22:59:22 +0300 Subject: [PATCH 28/86] pragma comment --- transfunctions/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/typing.py b/transfunctions/typing.py index f4d9550..be3f573 100644 --- a/transfunctions/typing.py +++ b/transfunctions/typing.py @@ -25,6 +25,6 @@ if sys.version_info >= (3, 9): IterableWithResults = Iterable[ReturnType] else: - IterableWithResults = Iterable + IterableWithResults = Iterable # pragma: no cover __all__ = ('ParamSpec', 'TypeAlias', 'Callable', 'Coroutine', 'Generator', 'ReturnType', 'FunctionParams', 'IterableWithResults', 'SomeClassInstance') From df11456dcb2a0086ec08d452799d8763463ff050 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 23:33:07 +0300 Subject: [PATCH 29/86] =?UTF-8?q?Add=20@=20prefix=20to=20dual=E2=80=91use?= =?UTF-8?q?=20decorator=20error=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- transfunctions/transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 5cc7ac3..4caaa90 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -213,7 +213,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] raise WrongDecoratorSyntaxError(f'The @{decorator_name} decorator cannot be used in conjunction with other decorators.') else: if transfunction_decorator is not None: - raise DualUseOfDecoratorError(f"You cannot use the '{decorator_name}' decorator twice for the same function.") + raise DualUseOfDecoratorError(f"You cannot use the @{decorator_name} decorator twice for the same function.") transfunction_decorator = decorator node.decorator_list = [] From fa840046d303917faa0a7058fce9024e81bc11b9 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 23:43:40 +0300 Subject: [PATCH 30/86] =?UTF-8?q?Add=20test=20for=20non=E2=80=91@=20usage?= =?UTF-8?q?=20of=20transfunction=20decorator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/units/decorators/test_transfunction.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 6f6785f..1630cb5 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -1532,3 +1532,19 @@ def template(number=SOME_GLOBAL): function = template.get_generator_function() assert list(function()) == ['kek'] + + +def test_use_decorator_without_at(): + def template(): + pass + + template = transfunction(template) + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + template.get_usual_function() + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + template.get_async_function() + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + template.get_generator_function() From a3685c2607ffa360d08fd397f4bb8fba21592e15 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 23:48:59 +0300 Subject: [PATCH 31/86] Add tests that disallow calling superfunction without @ --- tests/units/decorators/test_superfunction.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index 91cb701..3b04e56 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -763,3 +763,21 @@ def function(number=global_variable): yield number assert list(function()) == ['kek'] + + +def test_use_decorator_without_at(): + def template(): + pass + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + function = superfunction(template) + ~function() + + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + function = superfunction(template) + run(function()) + + with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + function = superfunction(template) + list(function()) From b2489df7789808e4588fcb2ade21775430937600 Mon Sep 17 00:00:00 2001 From: esblinov Date: Mon, 11 Aug 2025 23:50:21 +0300 Subject: [PATCH 32/86] delete extra code --- transfunctions/transformer.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 4caaa90..7f7cbf5 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -222,11 +222,6 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] RewriteContexts().visit(tree) DeleteDecorator().visit(tree) - if transfunction_decorator is None: - raise AliasedDecoratorSyntaxError( - "The transfunction decorator must have been renamed." - ) - function_def = cast(FunctionDef, tree.body[0]) if not function_def.body: function_def.body.append( From 4d63dfd130996cb2f5b688bb804665d4ab274551 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 00:06:31 +0300 Subject: [PATCH 33/86] Add automated Ollama code review workflow --- .github/workflows/code_review.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .github/workflows/code_review.yml diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml new file mode 100644 index 0000000..d743cb6 --- /dev/null +++ b/.github/workflows/code_review.yml @@ -0,0 +1,21 @@ +name: Automated Ollama Code Review + +on: + pull_request: + branches: + - main + +jobs: + ollama_review: + runs-on: ubuntu-latest + name: Ollama Code Review Job + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Ollama Code Review + uses: ./.github/actions/ollama-code-review + with: + llm-model: "codegemma" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 38ad864da2c491a6a151fb975e73d721e844e132 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 00:07:49 +0300 Subject: [PATCH 34/86] Remove unused AliasedDecoratorSyntaxError import --- transfunctions/transformer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 7f7cbf5..256b29d 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -29,7 +29,6 @@ from dill.source import getsource as dill_getsource # type: ignore[import-untyped] from transfunctions.errors import ( - AliasedDecoratorSyntaxError, CallTransfunctionDirectlyError, DualUseOfDecoratorError, WrongDecoratorSyntaxError, From c5e37a61b4d9ad1024704af4396efa30c2fd7d52 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 00:42:00 +0300 Subject: [PATCH 35/86] Improve some typing --- .github/workflows/lint.yml | 2 +- transfunctions/transformer.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d899894..379158d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - name: Run mypy shell: bash - run: mypy --check transfunctions + run: mypy transfunctions - name: Run mypy for tests shell: bash diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 256b29d..e6813e8 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -175,7 +175,7 @@ def extract_context(self, context_name: str, addictional_transformers: Optional[ converted_source_code = self.clear_spaces_from_source_code(source_code) tree = parse(converted_source_code) original_function = self.function - transfunction_decorator = None + transfunction_decorator: Optional[Name] = None decorator_name = self.decorator_name class RewriteContexts(NodeTransformer): @@ -213,7 +213,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] else: if transfunction_decorator is not None: raise DualUseOfDecoratorError(f"You cannot use the @{decorator_name} decorator twice for the same function.") - transfunction_decorator = decorator + transfunction_decorator = cast(Name, decorator) node.decorator_list = [] return node @@ -236,9 +236,9 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] tree = self.wrap_ast_by_closures(tree) if version_info.minor > 10: - increment_lineno(tree, n=(self.decorator_lineno - transfunction_decorator.lineno)) + increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno)) else: - increment_lineno(tree, n=(self.decorator_lineno - transfunction_decorator.lineno - 1)) + increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno - 1)) code = compile(tree, filename=getfile(self.function), mode='exec') namespace = UniversalNamespaceAroundFunction(self.function, self.frame) From 4cd93304b547cbf18c2d4a925769089c68dae9e0 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 00:47:40 +0300 Subject: [PATCH 36/86] rename a workflow --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index d743cb6..9faec03 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v2 - name: Ollama Code Review - uses: ./.github/actions/ollama-code-review + uses: ollama-code-review with: llm-model: "codegemma" env: From 95a857f21cfaaef3824856b5da5a05f0babffd5b Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 09:58:10 +0300 Subject: [PATCH 37/86] =?UTF-8?q?Use=20local=20ollama=E2=80=91code?= =?UTF-8?q?=E2=80=91review=20action=20in=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/ollama-code-review/action.yml | 38 +++++++++++++++++++ .github/workflows/code_review.yml | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 .github/actions/ollama-code-review/action.yml diff --git a/.github/actions/ollama-code-review/action.yml b/.github/actions/ollama-code-review/action.yml new file mode 100644 index 0000000..bfb150c --- /dev/null +++ b/.github/actions/ollama-code-review/action.yml @@ -0,0 +1,38 @@ +name: 'Code Review using Ollama' +description: 'Perform a code review on code modified using Ollama' +inputs: + llm-model: + description: 'Name of the LLM model to use for code review' + required: true + default: 'codegemma' +runs: + using: 'composite' + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Install Ollama + run: | + curl -fsSL https://ollama.com/install.sh | bash + ollama pull ${{ inputs.llm-model }} + shell: bash + + - name: Get modified files + id: get-modified-files + uses: tj-actions/changed-files@v43 + + - name: Review modified files + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + for file in ${{ steps.get-modified-files.outputs.all_changed_files }}; do + modified_file_review=$(curl -s http://127.0.0.1:11434/api/generate \ + -d '{"model": "${{ inputs.llm-model }}", "prompt": "Review the following file:\n\n```\n$(cat $file)\n```", "stream": false}' | jq -r '.response') + file_comment="Ollama Code Review for \`$file\`:\n\n$modified_file_review" + echo "$file_comment" >> ollama_review.txt + done + + gh pr comment ${{ github.event.pull_request.number }} --body "$(cat ollama_review.txt)" + shell: bash diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 9faec03..d743cb6 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -14,7 +14,7 @@ jobs: uses: actions/checkout@v2 - name: Ollama Code Review - uses: ollama-code-review + uses: ./.github/actions/ollama-code-review with: llm-model: "codegemma" env: From e4cc5a2ab4168b70343d20243719d44dede4dc46 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 14:23:10 +0300 Subject: [PATCH 38/86] Remove obsolete Ollama code review action and workflow --- .github/actions/ollama-code-review/action.yml | 38 ------------------- .github/workflows/code_review.yml | 21 ---------- 2 files changed, 59 deletions(-) delete mode 100644 .github/actions/ollama-code-review/action.yml delete mode 100644 .github/workflows/code_review.yml diff --git a/.github/actions/ollama-code-review/action.yml b/.github/actions/ollama-code-review/action.yml deleted file mode 100644 index bfb150c..0000000 --- a/.github/actions/ollama-code-review/action.yml +++ /dev/null @@ -1,38 +0,0 @@ -name: 'Code Review using Ollama' -description: 'Perform a code review on code modified using Ollama' -inputs: - llm-model: - description: 'Name of the LLM model to use for code review' - required: true - default: 'codegemma' -runs: - using: 'composite' - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Install Ollama - run: | - curl -fsSL https://ollama.com/install.sh | bash - ollama pull ${{ inputs.llm-model }} - shell: bash - - - name: Get modified files - id: get-modified-files - uses: tj-actions/changed-files@v43 - - - name: Review modified files - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - for file in ${{ steps.get-modified-files.outputs.all_changed_files }}; do - modified_file_review=$(curl -s http://127.0.0.1:11434/api/generate \ - -d '{"model": "${{ inputs.llm-model }}", "prompt": "Review the following file:\n\n```\n$(cat $file)\n```", "stream": false}' | jq -r '.response') - file_comment="Ollama Code Review for \`$file\`:\n\n$modified_file_review" - echo "$file_comment" >> ollama_review.txt - done - - gh pr comment ${{ github.event.pull_request.number }} --body "$(cat ollama_review.txt)" - shell: bash diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml deleted file mode 100644 index d743cb6..0000000 --- a/.github/workflows/code_review.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Automated Ollama Code Review - -on: - pull_request: - branches: - - main - -jobs: - ollama_review: - runs-on: ubuntu-latest - name: Ollama Code Review Job - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Ollama Code Review - uses: ./.github/actions/ollama-code-review - with: - llm-model: "codegemma" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From c3c65a6ef7a56d5d3d66adf85f8e17ea2a8dd705 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 14:28:32 +0300 Subject: [PATCH 39/86] Add Code Review workflow with Ollama LLM --- .github/workflows/code_review.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/code_review.yml diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml new file mode 100644 index 0000000..b84b38b --- /dev/null +++ b/.github/workflows/code_review.yml @@ -0,0 +1,26 @@ +# https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ +name: Code Review +on: pull_request + +permissions: + contents: read + pull-requests: write + +jobs: + code-review: + runs-on: ubuntu-latest + steps: + - name: Setup ollama + uses: ai-action/setup-ollama@v1 + + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Code review comment + run: | + PROMPT='Code review the changes below:' + RESPONSE=$(ollama run codellama "$PROMPT\n$(gh pr diff $PR_NUMBER)") + gh pr comment $PR_NUMBER --body "$RESPONSE" + env: + GH_TOKEN: ${{ github.token }} + PR_NUMBER: ${{ github.event.pull_request.number }} From caed76a84c8b998cba457e546dc5d64b990ffd7e Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 14:34:00 +0300 Subject: [PATCH 40/86] Update code_review workflow to run on pull request to main --- .github/workflows/code_review.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index b84b38b..e5e1832 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -1,6 +1,10 @@ # https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ name: Code Review -on: pull_request + +on: + pull_request: + branches: + - main permissions: contents: read From 1548ce9c2139528ca8f39a9394b2ab803c8e729e Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 16:55:21 +0300 Subject: [PATCH 41/86] Add node_modules to .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a21fce1..c4d5adc 100644 --- a/.gitignore +++ b/.gitignore @@ -14,5 +14,5 @@ uv.lock .history .vscode/ .idea/ -temp* .ropeproject +node_modules From f65f7ecf6f3edac42cf98c7d9612f0582d7f01d0 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 17:18:23 +0300 Subject: [PATCH 42/86] Update code review prompt for style and security --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index e5e1832..3a3806c 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -22,7 +22,7 @@ jobs: - name: Code review comment run: | - PROMPT='Code review the changes below:' + PROMPT='Here is the code, find all the problems and flaws in it, pay attention to both stylistic flaws and vulnerabilities:' RESPONSE=$(ollama run codellama "$PROMPT\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: From 7d4ce300915a8eddc6db6c3b6589bf76f715004c Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 17:57:06 +0300 Subject: [PATCH 43/86] Cache and pull codellama model in CI workflow --- .github/workflows/code_review.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 3a3806c..23610c0 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -14,12 +14,28 @@ jobs: code-review: runs-on: ubuntu-latest steps: + # Restore cached Ollama models to speed up CI runs + - name: Restore Ollama model cache + uses: actions/cache@v4 + with: + path: ~/.ollama/models + key: ${{ runner.os }}-codellama-model + restore-keys: | + ${{ runner.os }}-codellama- + + # Install ollama - name: Setup ollama uses: ai-action/setup-ollama@v1 + # Pull the codellama model if it hasn't been cached yet + - name: Download codellama model + run: ollama pull codellama + + # Checkout repository - name: Checkout repository uses: actions/checkout@v4 + # Run code review and comment on PR - name: Code review comment run: | PROMPT='Here is the code, find all the problems and flaws in it, pay attention to both stylistic flaws and vulnerabilities:' From 2754d4ae3f61e2e87463ff61b0750dfaf5e6bc4e Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 18:03:59 +0300 Subject: [PATCH 44/86] Update code review prompt to focus on vulnerabilities --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 23610c0..1f44331 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -38,7 +38,7 @@ jobs: # Run code review and comment on PR - name: Code review comment run: | - PROMPT='Here is the code, find all the problems and flaws in it, pay attention to both stylistic flaws and vulnerabilities:' + PROMPT='Now I'll give you the code (actually it's just a diff), your task is to find all the vulnerabilities and flaws in it that you can find. Report each vulnerability only if you are SURE that it is indeed a vulnerability. Accompany each item with a markdown block with an example code that would fix this problem, if you can think of one. So, here's the code:' RESPONSE=$(ollama run codellama "$PROMPT\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: From bb19e92dbf1a18ef1718b52d7b652fe5c612c714 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 18:40:57 +0300 Subject: [PATCH 45/86] Update code_review.yml --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 1f44331..99b4725 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -38,7 +38,7 @@ jobs: # Run code review and comment on PR - name: Code review comment run: | - PROMPT='Now I'll give you the code (actually it's just a diff), your task is to find all the vulnerabilities and flaws in it that you can find. Report each vulnerability only if you are SURE that it is indeed a vulnerability. Accompany each item with a markdown block with an example code that would fix this problem, if you can think of one. So, here's the code:' + PROMPT="Now I'll give you the code (actually it's just a diff), your task is to find all the vulnerabilities and flaws in it that you can find. Report each vulnerability only if you are SURE that it is indeed a vulnerability. Accompany each item with a markdown block with an example code that would fix this problem, if you can think of one. So, here's the code:" RESPONSE=$(ollama run codellama "$PROMPT\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: From 7bd1757ae5cc93d7c7df3122faf4285fd8979177 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 19:22:43 +0300 Subject: [PATCH 46/86] Update code review workflow to use Qwen 2.5 Coder Tools model --- .github/workflows/code_review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 99b4725..5afa5ee 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -29,7 +29,7 @@ jobs: # Pull the codellama model if it hasn't been cached yet - name: Download codellama model - run: ollama pull codellama + run: ollama pull hhao/qwen2.5-coder-tools:0.5b # Checkout repository - name: Checkout repository @@ -39,7 +39,7 @@ jobs: - name: Code review comment run: | PROMPT="Now I'll give you the code (actually it's just a diff), your task is to find all the vulnerabilities and flaws in it that you can find. Report each vulnerability only if you are SURE that it is indeed a vulnerability. Accompany each item with a markdown block with an example code that would fix this problem, if you can think of one. So, here's the code:" - RESPONSE=$(ollama run codellama "$PROMPT\n$(gh pr diff $PR_NUMBER)") + RESPONSE=$(ollama run hhao/qwen2.5-coder-tools:0.5b "$PROMPT\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: GH_TOKEN: ${{ github.token }} From 6725beb205f0367390c6d03baec4c328a3dcd23d Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 19:36:40 +0300 Subject: [PATCH 47/86] Refine code review prompt for safety --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 5afa5ee..66399a2 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -38,7 +38,7 @@ jobs: # Run code review and comment on PR - name: Code review comment run: | - PROMPT="Now I'll give you the code (actually it's just a diff), your task is to find all the vulnerabilities and flaws in it that you can find. Report each vulnerability only if you are SURE that it is indeed a vulnerability. Accompany each item with a markdown block with an example code that would fix this problem, if you can think of one. So, here's the code:" + PROMPT="you are given a code (diff from github to be exact). Assume that it's ok by default and answer with 'No problems has been found' if you don't see any critical problems. If you are sure that you spotted a 100% problem, please double-check if you are correct, then give a short answer as to what the problem is. Rarely, you may spot multiple problems. If so, use markdown blocks for each item. Specify extra details only if critically nesseccery. Here's the code given:" RESPONSE=$(ollama run hhao/qwen2.5-coder-tools:0.5b "$PROMPT\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: From 3341155bbec14501f1d851084ac2495a05fe668b Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 19:49:55 +0300 Subject: [PATCH 48/86] Add model env and bot name to code review --- .github/workflows/code_review.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 66399a2..78a402c 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -1,5 +1,5 @@ # https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ -name: Code Review +name: A very stupid code review on: pull_request: @@ -10,12 +10,15 @@ permissions: contents: read pull-requests: write +env: + MODEL: hhao/qwen2.5-coder-tools:0.5b + BOT_NAME: Stupid bot + jobs: code-review: runs-on: ubuntu-latest steps: - # Restore cached Ollama models to speed up CI runs - - name: Restore Ollama model cache + - name: Restore the model cache uses: actions/cache@v4 with: path: ~/.ollama/models @@ -27,9 +30,9 @@ jobs: - name: Setup ollama uses: ai-action/setup-ollama@v1 - # Pull the codellama model if it hasn't been cached yet - - name: Download codellama model - run: ollama pull hhao/qwen2.5-coder-tools:0.5b + # Pull the model if it hasn't been cached yet + - name: Download the model + run: ollama pull ${{ env.MODEL }} # Checkout repository - name: Checkout repository @@ -39,8 +42,8 @@ jobs: - name: Code review comment run: | PROMPT="you are given a code (diff from github to be exact). Assume that it's ok by default and answer with 'No problems has been found' if you don't see any critical problems. If you are sure that you spotted a 100% problem, please double-check if you are correct, then give a short answer as to what the problem is. Rarely, you may spot multiple problems. If so, use markdown blocks for each item. Specify extra details only if critically nesseccery. Here's the code given:" - RESPONSE=$(ollama run hhao/qwen2.5-coder-tools:0.5b "$PROMPT\n$(gh pr diff $PR_NUMBER)") - gh pr comment $PR_NUMBER --body "$RESPONSE" + RESPONSE=$(ollama run ${{ env.MODEL }} "$PROMPT\n$(gh pr diff $PR_NUMBER)") + gh pr comment $PR_NUMBER --body "$RESPONSE" --author ${{ env.BOT_NAME }} env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} From 5e3b4f8828657f10b16526e6b20452018b3b49e5 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 20:03:52 +0300 Subject: [PATCH 49/86] Update code review workflow to use local LLM --- .github/workflows/code_review.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 78a402c..778a2b4 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -1,4 +1,5 @@ -# https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ +# This workflow runs a locally‑hosted LLM inside the runner to generate critical comments on pull requests +# Based on: https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ name: A very stupid code review on: From a1a60be578f1a24589dcd5f27f4a4bde777f3dc6 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 20:23:05 +0300 Subject: [PATCH 50/86] Use MODEL env var for cache key in code_review --- .github/workflows/code_review.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 778a2b4..58a298d 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -23,9 +23,9 @@ jobs: uses: actions/cache@v4 with: path: ~/.ollama/models - key: ${{ runner.os }}-codellama-model + key: ${{ runner.os }}-${{ env.MODEL }} restore-keys: | - ${{ runner.os }}-codellama- + ${{ runner.os }}-${{ env.MODEL }} # Install ollama - name: Setup ollama From ccd864d429154fc771f5278c4cb2756dce771117 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 12 Aug 2025 22:08:16 +0300 Subject: [PATCH 51/86] Remove explicit bot author from PR comments --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 58a298d..c0d612b 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -44,7 +44,7 @@ jobs: run: | PROMPT="you are given a code (diff from github to be exact). Assume that it's ok by default and answer with 'No problems has been found' if you don't see any critical problems. If you are sure that you spotted a 100% problem, please double-check if you are correct, then give a short answer as to what the problem is. Rarely, you may spot multiple problems. If so, use markdown blocks for each item. Specify extra details only if critically nesseccery. Here's the code given:" RESPONSE=$(ollama run ${{ env.MODEL }} "$PROMPT\n$(gh pr diff $PR_NUMBER)") - gh pr comment $PR_NUMBER --body "$RESPONSE" --author ${{ env.BOT_NAME }} + gh pr comment $PR_NUMBER --body "$RESPONSE" env: GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} From c197b174f235529ed4b5ee65d4271beb43f20f47 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 10:02:29 +0300 Subject: [PATCH 52/86] Update CI workflows to newer action versions and correct cache paths --- .github/workflows/code_review.yml | 2 +- .github/workflows/lint.yml | 6 +++--- .github/workflows/release.yml | 6 +++--- .github/workflows/tests_and_coverage.yml | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index c0d612b..7d2faa1 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -22,7 +22,7 @@ jobs: - name: Restore the model cache uses: actions/cache@v4 with: - path: ~/.ollama/models + path: ~/.ollama key: ${{ runner.os }}-${{ env.MODEL }} restore-keys: | ${{ runner.os }}-${{ env.MODEL }} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 379158d..60c3ccb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,15 +10,15 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Cache pip dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fd1689b..8b3e1df 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,12 +15,12 @@ jobs: # IMPORTANT: this permission is mandatory for trusted publishing id-token: write steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 + uses: actions/setup-python@v5 with: - python-version: ${{ matrix.python-version }} + python-version: ${{ matrix.python-version }} - name: Install dependencies shell: bash diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index 817b5b3..7da8c04 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -11,9 +11,9 @@ jobs: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} @@ -22,7 +22,7 @@ jobs: run: pip install . - name: Cache pip dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.cache/pip key: ${{ runner.os }}-pip-${{ github.workflow }}-${{ hashFiles('requirements_dev.txt') }} From ccd69c7182a8bd27100be4aa700c74500a2ca2a2 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 10:05:43 +0300 Subject: [PATCH 53/86] Update CODE REVIEW workflow to use new Qwen3 model --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 7d2faa1..a73e8d7 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -12,7 +12,7 @@ permissions: pull-requests: write env: - MODEL: hhao/qwen2.5-coder-tools:0.5b + MODEL: kirito1/qwen3-coder:4b BOT_NAME: Stupid bot jobs: From e36aa315ef5f2e95ae1df4572dd4ec48e92afad9 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 10:47:00 +0300 Subject: [PATCH 54/86] Add detailed code review prompt to workflow --- .github/workflows/code_review.yml | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index a73e8d7..a8f3f7a 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -14,6 +14,19 @@ permissions: env: MODEL: kirito1/qwen3-coder:4b BOT_NAME: Stupid bot + PROMPT: | + Please review the code. + + Consider: + 1. Code quality and adherence to best practices + 2. Potential bugs or edge cases + 3. Performance optimizations + 4. Readability and maintainability + 5. Any security concerns + + Suggest improvements and explain your reasoning for each suggestion. + + The code is following: jobs: code-review: @@ -42,8 +55,7 @@ jobs: # Run code review and comment on PR - name: Code review comment run: | - PROMPT="you are given a code (diff from github to be exact). Assume that it's ok by default and answer with 'No problems has been found' if you don't see any critical problems. If you are sure that you spotted a 100% problem, please double-check if you are correct, then give a short answer as to what the problem is. Rarely, you may spot multiple problems. If so, use markdown blocks for each item. Specify extra details only if critically nesseccery. Here's the code given:" - RESPONSE=$(ollama run ${{ env.MODEL }} "$PROMPT\n$(gh pr diff $PR_NUMBER)") + RESPONSE=$(ollama run ${{ env.MODEL }} "${{ env.PROMPT }}\n$(gh pr diff $PR_NUMBER)") gh pr comment $PR_NUMBER --body "$RESPONSE" env: GH_TOKEN: ${{ github.token }} From 1cdac791279796b47039caeafd0722259d3608da Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 10:47:16 +0300 Subject: [PATCH 55/86] Remove BOT_NAME from workflow environment variables --- .github/workflows/code_review.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index a8f3f7a..babf61d 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -13,7 +13,6 @@ permissions: env: MODEL: kirito1/qwen3-coder:4b - BOT_NAME: Stupid bot PROMPT: | Please review the code. From dff2135f7b50d7138b5f1deab40abb16c340d6a1 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 10:48:39 +0300 Subject: [PATCH 56/86] Add comment about prompt source to code_review workflow --- .github/workflows/code_review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index babf61d..37c0111 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -1,5 +1,6 @@ # This workflow runs a locally‑hosted LLM inside the runner to generate critical comments on pull requests # Based on: https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ +# The prompt is taken from here: https://www.reddit.com/r/ChatGPTCoding/comments/1f51y8s/a_collection_of_prompts_for_generating_high/ name: A very stupid code review on: From dd8a1b18e0f1af5737fbbca865f7881416f399f6 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 11:33:50 +0300 Subject: [PATCH 57/86] Update code_review.yml --- .github/workflows/code_review.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 37c0111..3961513 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -15,18 +15,18 @@ permissions: env: MODEL: kirito1/qwen3-coder:4b PROMPT: | - Please review the code. + You are a code quality control agent in the repository. Your task is to identify the following problems: - Consider: 1. Code quality and adherence to best practices 2. Potential bugs or edge cases 3. Performance optimizations 4. Readability and maintainability 5. Any security concerns - Suggest improvements and explain your reasoning for each suggestion. + Only if you are absolutely sure that there is a problem (check it twice) - write about it. You must explain the problem clearly, but be as short and concise as possible. + To clarify your thoughts, you can use blocks of code in the markdown format, but do not abuse it, use it only if you cannot convey your thoughts in a shorter way. - The code is following: + Here’s the code given (or rather, only a diff from PR): jobs: code-review: From 6941cc13fd00ee14cf8dfb7532872444ef73c863 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 12:03:34 +0300 Subject: [PATCH 58/86] =?UTF-8?q?Add=20guidance=20for=20=E2=80=9Ceverythin?= =?UTF-8?q?g=20fine=E2=80=9D=20comment=20in=20Code=20Review=20workflow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/code_review.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 3961513..1e1f657 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -26,6 +26,8 @@ env: Only if you are absolutely sure that there is a problem (check it twice) - write about it. You must explain the problem clearly, but be as short and concise as possible. To clarify your thoughts, you can use blocks of code in the markdown format, but do not abuse it, use it only if you cannot convey your thoughts in a shorter way. + If everything is fine with the code and you don't see any obvious errors, just write: "Everything seems to be fine with the code!". Don't write anything else in this case. + Here’s the code given (or rather, only a diff from PR): jobs: From e0bd4d88ce533dc56083db08bb366b542a109ff5 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 16:04:25 +0300 Subject: [PATCH 59/86] Add senior developer role to code review prompt --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 1e1f657..8644dfe 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -15,7 +15,7 @@ permissions: env: MODEL: kirito1/qwen3-coder:4b PROMPT: | - You are a code quality control agent in the repository. Your task is to identify the following problems: + You are a senior developer and an attentive code quality control agent in the repository. Your task is to read the code and pay attention to the following things: 1. Code quality and adherence to best practices 2. Potential bugs or edge cases From 7a5eb30492d7c1073c30f23e19b32f0045a7a4e4 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 16:28:04 +0300 Subject: [PATCH 60/86] Show PROMPT during code review workflow --- .github/workflows/code_review.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 8644dfe..8b93119 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -54,7 +54,10 @@ jobs: - name: Checkout repository uses: actions/checkout@v4 - # Run code review and comment on PR + - name: Show PROMPT + run: | + echo "PROMPT: ${{ env.PROMPT }}" + - name: Code review comment run: | RESPONSE=$(ollama run ${{ env.MODEL }} "${{ env.PROMPT }}\n$(gh pr diff $PR_NUMBER)") From 469270e9c774e4510430b73c9469c9cfc0f97634 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 16:44:18 +0300 Subject: [PATCH 61/86] Add workflow dispatch with reason input --- .github/workflows/code_review.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 8b93119..3dc20b4 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -7,6 +7,12 @@ on: pull_request: branches: - main + workflow_dispatch: + inputs: + reason: + description: "Причина запуска" + required: false + type: string permissions: contents: read From df250c929b5e894c29764fb55cb3746beb3cb126 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 13 Aug 2025 17:15:14 +0300 Subject: [PATCH 62/86] Update code review workflow input description to English --- .github/workflows/code_review.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml index 3dc20b4..631e6eb 100644 --- a/.github/workflows/code_review.yml +++ b/.github/workflows/code_review.yml @@ -10,7 +10,7 @@ on: workflow_dispatch: inputs: reason: - description: "Причина запуска" + description: "Run stupid code-review" required: false type: string From 0ec0fe93df08fe9c53661ab00d97395da454d247 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 19 Aug 2025 07:49:18 +0300 Subject: [PATCH 63/86] try python 3.14 in the CI --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 60c3ccb..33b9b6b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 From 082066be2b59c5b6ede17720e4290b66e6741791 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 19 Aug 2025 07:52:57 +0300 Subject: [PATCH 64/86] =?UTF-8?q?Update=20lint=20workflow=20to=20include?= =?UTF-8?q?=203.14=E2=80=91dev=20Python=20version=20in=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/lint.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 33b9b6b..d1100cb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,7 +7,8 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14"] + python-version: + ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"] steps: - uses: actions/checkout@v4 From 54fc281aa8f206b594e26bc555d1eb4ca311f8a1 Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 19 Aug 2025 08:03:10 +0300 Subject: [PATCH 65/86] =?UTF-8?q?Add=20Python=203.14=E2=80=91dev=20to=20CI?= =?UTF-8?q?=20matrix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/tests_and_coverage.yml | 3 ++- pyproject.toml | 13 +++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index 7da8c04..f6a088a 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -8,7 +8,8 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: + ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev", "3.14-dev"] steps: - uses: actions/checkout@v4 diff --git a/pyproject.toml b/pyproject.toml index 54c151f..7b8fa89 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,9 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "transfunctions" version = "0.0.9" -authors = [ - { name="Evgeniy Blinov", email="zheni-b@yandex.ru" }, -] +authors = [{ name = "Evgeniy Blinov", email = "zheni-b@yandex.ru" }] description = 'Say NO to Python fragmentation on sync and async' readme = "README.md" requires-python = ">=3.8" @@ -30,6 +28,7 @@ classifiers = [ 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', @@ -50,13 +49,11 @@ keywords = [ "transfunctions" = ["py.typed"] [tool.mutmut] -paths_to_mutate="transfunctions" -runner="pytest" +paths_to_mutate = "transfunctions" +runner = "pytest" [tool.pytest.ini_options] -markers = [ - "mypy_testing", -] +markers = ["mypy_testing"] [project.urls] 'Source' = 'https://github.com/pomponchik/transfunctions' From f16cbbe79750341ecca44cf3af338422d286271f Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 16 Oct 2025 13:54:57 +0300 Subject: [PATCH 66/86] Clarify removal/addition wording in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9a1a533..75f16c0 100644 --- a/README.md +++ b/README.md @@ -309,4 +309,4 @@ There are 2 main difficulties in developing typing here: - Code generation creates code in runtime that is not in the source files of your project. Whereas most type analyzers look at your code statically, at what is actually present in your files. - We mix several types of syntax in a single template function, but the static analyzer does not know that this is a template and part of the code will be deleted from here. In its opinion, this is the final function that will continue to be used in your project. -As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly remove code from functions, but hardly add it there during code generation. In other words, we almost never encounter the problem of how to type the *added* code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. +As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly *remove* code from functions, but hardly *add* it there during code generation. In other words, we almost never encounter the problem of how to type the *added* code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. From a5639b2cbb4a84c6597c043d7e9dd7956f60d614 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Mon, 9 Feb 2026 22:56:52 +0300 Subject: [PATCH 67/86] Fix typo in issue template labels --- .github/ISSUE_TEMPLATE/question.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 1a8a31c..40fe808 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -2,7 +2,7 @@ name: Question or consultation about: Ask anything about this project title: '' -labels: guestion +labels: question assignees: pomponchik --- From 3808361b3df9a6ad59c065722aa161e5a7114024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Mon, 9 Feb 2026 22:59:28 +0300 Subject: [PATCH 68/86] Update Python versions in CI workflows to remove "3.14-dev" and add "3.14t" --- .github/workflows/lint.yml | 3 +-- .github/workflows/tests_and_coverage.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index d1100cb..74dccac 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,8 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: - ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/tests_and_coverage.yml b/.github/workflows/tests_and_coverage.yml index f6a088a..5cd691f 100644 --- a/.github/workflows/tests_and_coverage.yml +++ b/.github/workflows/tests_and_coverage.yml @@ -8,8 +8,7 @@ jobs: strategy: matrix: os: [macos-latest, ubuntu-latest, windows-latest] - python-version: - ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14-dev", "3.14-dev"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t"] steps: - uses: actions/checkout@v4 From d9aee0aab29d52b37eb1ab4910416af32d9bd1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 00:54:42 +0300 Subject: [PATCH 69/86] Add Python Free Threading classifiers --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 7b8fa89..068685b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,6 +29,8 @@ classifiers = [ 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: Free Threading', + 'Programming Language :: Python :: Free Threading :: 3 - Stable', 'License :: OSI Approved :: MIT License', 'Intended Audience :: Developers', 'Topic :: Software Development :: Libraries', From 2d0ff37a4655f856374839cdd66c5dfa8b72afb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:00:27 +0300 Subject: [PATCH 70/86] Remove code review workflow using local LLM --- .github/workflows/code_review.yml | 73 ------------------------------- 1 file changed, 73 deletions(-) delete mode 100644 .github/workflows/code_review.yml diff --git a/.github/workflows/code_review.yml b/.github/workflows/code_review.yml deleted file mode 100644 index 631e6eb..0000000 --- a/.github/workflows/code_review.yml +++ /dev/null @@ -1,73 +0,0 @@ -# This workflow runs a locally‑hosted LLM inside the runner to generate critical comments on pull requests -# Based on: https://remarkablemark.org/blog/2025/02/23/run-ollama-large-language-models-on-github-actions/ -# The prompt is taken from here: https://www.reddit.com/r/ChatGPTCoding/comments/1f51y8s/a_collection_of_prompts_for_generating_high/ -name: A very stupid code review - -on: - pull_request: - branches: - - main - workflow_dispatch: - inputs: - reason: - description: "Run stupid code-review" - required: false - type: string - -permissions: - contents: read - pull-requests: write - -env: - MODEL: kirito1/qwen3-coder:4b - PROMPT: | - You are a senior developer and an attentive code quality control agent in the repository. Your task is to read the code and pay attention to the following things: - - 1. Code quality and adherence to best practices - 2. Potential bugs or edge cases - 3. Performance optimizations - 4. Readability and maintainability - 5. Any security concerns - - Only if you are absolutely sure that there is a problem (check it twice) - write about it. You must explain the problem clearly, but be as short and concise as possible. - To clarify your thoughts, you can use blocks of code in the markdown format, but do not abuse it, use it only if you cannot convey your thoughts in a shorter way. - - If everything is fine with the code and you don't see any obvious errors, just write: "Everything seems to be fine with the code!". Don't write anything else in this case. - - Here’s the code given (or rather, only a diff from PR): - -jobs: - code-review: - runs-on: ubuntu-latest - steps: - - name: Restore the model cache - uses: actions/cache@v4 - with: - path: ~/.ollama - key: ${{ runner.os }}-${{ env.MODEL }} - restore-keys: | - ${{ runner.os }}-${{ env.MODEL }} - - # Install ollama - - name: Setup ollama - uses: ai-action/setup-ollama@v1 - - # Pull the model if it hasn't been cached yet - - name: Download the model - run: ollama pull ${{ env.MODEL }} - - # Checkout repository - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Show PROMPT - run: | - echo "PROMPT: ${{ env.PROMPT }}" - - - name: Code review comment - run: | - RESPONSE=$(ollama run ${{ env.MODEL }} "${{ env.PROMPT }}\n$(gh pr diff $PR_NUMBER)") - gh pr comment $PR_NUMBER --body "$RESPONSE" - env: - GH_TOKEN: ${{ github.token }} - PR_NUMBER: ${{ github.event.pull_request.number }} From 5ff8e91fba0178878f6e23a9061fc903eeccc5df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:09:44 +0300 Subject: [PATCH 71/86] Bump displayhooks to 0.0.5 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 068685b..ee0f510 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,7 @@ description = 'Say NO to Python fragmentation on sync and async' readme = "README.md" requires-python = ">=3.8" dependencies = [ - 'displayhooks>=0.0.4', + 'displayhooks>=0.0.5', 'dill==0.4.0', 'typing_extensions ; python_version <= "3.10"', ] From 742a077fc6160f76b68ce8bfe772321db76dfc19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:12:34 +0300 Subject: [PATCH 72/86] Add check_decorators parameter to superfunction and transfunction decorators --- transfunctions/decorators/superfunction.py | 5 +++-- transfunctions/decorators/transfunction.py | 1 + transfunctions/transformer.py | 9 ++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index 56b6638..f0b8f67 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -99,12 +99,12 @@ def superfunction(function: Callable[FunctionParams, ReturnType]) -> Callable[Fu @overload def superfunction( - *, tilde_syntax: bool = True + *, tilde_syntax: bool = True, check_decorators: bool = True ) -> Callable[[Callable[FunctionParams, ReturnType]], Callable[FunctionParams, UsageTracer[FunctionParams, ReturnType]]]: ... def superfunction( # type: ignore[misc] - *args: Callable[FunctionParams, ReturnType], tilde_syntax: bool = True + *args: Callable[FunctionParams, ReturnType], tilde_syntax: bool = True, check_decorators: bool = True, ) -> Union[ Callable[FunctionParams, UsageTracer[FunctionParams, ReturnType]], Callable[[Callable[FunctionParams, ReturnType]], Callable[FunctionParams, UsageTracer[FunctionParams, ReturnType]]], @@ -115,6 +115,7 @@ def decorator(function: Callable[FunctionParams, ReturnType]) -> Callable[Functi cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, "superfunction", cast(FrameType, cast(FrameType, currentframe()).f_back), + check_decorators, ) if not tilde_syntax: diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index 46d4c16..f19fb0e 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -14,4 +14,5 @@ def transfunction( cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, "transfunction", cast(FrameType, cast(FrameType, currentframe()).f_back), + True, ) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index e6813e8..9a319b2 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -40,9 +40,9 @@ class FunctionTransformer(Generic[FunctionParams, ReturnType]): def __init__( - self, function: Callable[FunctionParams, ReturnType], decorator_lineno: int, decorator_name: str, frame: FrameType, + self, function: Callable[FunctionParams, ReturnType], decorator_lineno: int, decorator_name: str, frame: FrameType, check_decorators: bool, ) -> None: - if isinstance(function, type(self)): + if isinstance(function, type(self)) and check_decorators: raise DualUseOfDecoratorError(f"You cannot use the '{decorator_name}' decorator twice for the same function.") if not isfunction(function): raise ValueError(f"Only regular or generator functions can be used as a template for @{decorator_name}.") @@ -55,6 +55,7 @@ def __init__( self.decorator_lineno = decorator_lineno self.decorator_name = decorator_name self.frame = frame + self.check_decorators = check_decorators self.base_object: Optional[SomeClassInstance] = None # type: ignore[valid-type] self.cache: Dict[str, Callable[FunctionParams, ReturnType]] = {} @@ -177,6 +178,7 @@ def extract_context(self, context_name: str, addictional_transformers: Optional[ original_function = self.function transfunction_decorator: Optional[Name] = None decorator_name = self.decorator_name + check_decorators = self.check_decorators class RewriteContexts(NodeTransformer): def visit_With(self, node: With) -> Optional[Union[AST, List[AST]]]: @@ -198,7 +200,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] nonlocal transfunction_decorator transfunction_decorator = None - if not node.decorator_list: + if not node.decorator_list and check_decorators: raise WrongDecoratorSyntaxError(f"The @{decorator_name} decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.") for decorator in node.decorator_list: @@ -208,6 +210,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] if ( isinstance(decorator, Name) and decorator.id != decorator_name + and check_decorators ): raise WrongDecoratorSyntaxError(f'The @{decorator_name} decorator cannot be used in conjunction with other decorators.') else: From bc0064c3f438cddfee8190d0648e00a7e7c22e51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:14:21 +0300 Subject: [PATCH 73/86] Move Ruff config from .ruff.toml to pyproject.toml --- .ruff.toml | 4 ---- pyproject.toml | 5 +++++ 2 files changed, 5 insertions(+), 4 deletions(-) delete mode 100644 .ruff.toml diff --git a/.ruff.toml b/.ruff.toml deleted file mode 100644 index 353bb74..0000000 --- a/.ruff.toml +++ /dev/null @@ -1,4 +0,0 @@ -lint.ignore = ['E501', 'E712'] - -[format] -quote-style = "single" diff --git a/pyproject.toml b/pyproject.toml index ee0f510..fed1a33 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,11 @@ runner = "pytest" [tool.pytest.ini_options] markers = ["mypy_testing"] +[tool.ruff] +lint.ignore = ['E501', 'E712', 'PTH123', 'PTH118', 'PLR2004', 'PTH107', 'SIM105', 'SIM102', 'RET503', 'PLR0912', 'C901'] +lint.select = ["ERA001", "YTT", "ASYNC", "BLE", "B", "A", "COM", "INP", "PIE", "T20", "PT", "RSE", "RET", "SIM", "SLOT", "TID252", "ARG", "PTH", "I", "C90", "N", "E", "W", "D201", "D202", "D419", "F", "PL", "PLE", "PLR", "PLW", "RUF", "TRY201", "TRY400", "TRY401"] +format.quote-style = "single" + [project.urls] 'Source' = 'https://github.com/pomponchik/transfunctions' 'Tracker' = 'https://github.com/pomponchik/transfunctions/issues' From e83d9066961df3b26a85d6a220060f88f444fce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:14:34 +0300 Subject: [PATCH 74/86] Lint issues --- tests/documentation/test_readme.py | 4 +-- .../decorators/test_superfunction_typing.py | 11 ++++-- .../decorators/test_transfunction_typing.py | 2 +- tests/units/decorators/test_superfunction.py | 18 +++++++--- tests/units/decorators/test_transfunction.py | 19 +++++++--- tests/units/test_universal_namespace.py | 3 +- transfunctions/__init__.py | 36 +++++++++++++------ transfunctions/decorators/superfunction.py | 17 +++++---- transfunctions/decorators/transfunction.py | 2 +- transfunctions/markers.py | 2 +- transfunctions/transformer.py | 24 ++++++++----- transfunctions/typing.py | 4 +-- transfunctions/universal_namespace.py | 4 +-- 13 files changed, 97 insertions(+), 49 deletions(-) diff --git a/tests/documentation/test_readme.py b/tests/documentation/test_readme.py index 47f01ab..631e752 100644 --- a/tests/documentation/test_readme.py +++ b/tests/documentation/test_readme.py @@ -3,10 +3,10 @@ from contextlib import redirect_stdout from transfunctions import ( - transfunction, - sync_context, async_context, generator_context, + sync_context, + transfunction, ) diff --git a/tests/typing/decorators/test_superfunction_typing.py b/tests/typing/decorators/test_superfunction_typing.py index 84cb2ae..cab1b29 100644 --- a/tests/typing/decorators/test_superfunction_typing.py +++ b/tests/typing/decorators/test_superfunction_typing.py @@ -1,5 +1,5 @@ -import sys import asyncio +import sys from contextlib import suppress if sys.version_info <= (3, 11): @@ -9,8 +9,13 @@ import pytest -from transfunctions import superfunction, sync_context, async_context, generator_context, yield_from_it - +from transfunctions import ( + async_context, + generator_context, + superfunction, + sync_context, + yield_from_it, +) """ Что нужно проверить: diff --git a/tests/typing/decorators/test_transfunction_typing.py b/tests/typing/decorators/test_transfunction_typing.py index 8c10822..ec88800 100644 --- a/tests/typing/decorators/test_transfunction_typing.py +++ b/tests/typing/decorators/test_transfunction_typing.py @@ -9,7 +9,7 @@ import pytest -from transfunctions import transfunction, sync_context, async_context +from transfunctions import async_context, sync_context, transfunction @pytest.mark.mypy_testing diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index 3b04e56..a41f710 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -3,10 +3,20 @@ from asyncio import run from contextlib import redirect_stdout -import pytest import full_match +import pytest -from transfunctions import superfunction, sync_context, async_context, generator_context, await_it, yield_from_it, WrongDecoratorSyntaxError, WrongTransfunctionSyntaxError, WrongMarkerSyntaxError +from transfunctions import ( + WrongDecoratorSyntaxError, + WrongMarkerSyntaxError, + WrongTransfunctionSyntaxError, + async_context, + await_it, + generator_context, + superfunction, + sync_context, + yield_from_it, +) """ Что нужно проверить: @@ -325,7 +335,7 @@ def function(): function() - assert 'The tilde-syntax is enabled for the "function" function. Call it like this: ~function().' == exception_message + assert exception_message == 'The tilde-syntax is enabled for the "function" function. Call it like this: ~function().' sys.unraisablehook = old_hook @@ -344,7 +354,7 @@ def function(): function() - assert 'The tilde-syntax is enabled for the "function" function. Call it like this: ~function().' == exception_message + assert exception_message == 'The tilde-syntax is enabled for the "function" function. Call it like this: ~function().' sys.unraisablehook = old_hook diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 1630cb5..05fd4be 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -1,15 +1,24 @@ import traceback -from inspect import isfunction, iscoroutinefunction, isgeneratorfunction, getsourcelines from asyncio import run from contextlib import contextmanager +from inspect import getsourcelines, iscoroutinefunction, isfunction, isgeneratorfunction -import pytest import full_match +import pytest -from transfunctions import transfunction, CallTransfunctionDirectlyError, WrongDecoratorSyntaxError, DualUseOfDecoratorError, WrongMarkerSyntaxError +from transfunctions import ( + CallTransfunctionDirectlyError, + DualUseOfDecoratorError, + WrongDecoratorSyntaxError, + WrongMarkerSyntaxError, + async_context, + await_it, + generator_context, + sync_context, + transfunction, + yield_from_it, +) from transfunctions.transformer import FunctionTransformer -from transfunctions import async_context, sync_context, generator_context, yield_from_it, await_it - SOME_GLOBAL = 777 diff --git a/tests/units/test_universal_namespace.py b/tests/units/test_universal_namespace.py index 58318cc..910151e 100644 --- a/tests/units/test_universal_namespace.py +++ b/tests/units/test_universal_namespace.py @@ -1,11 +1,10 @@ -from inspect import currentframe import builtins +from inspect import currentframe import pytest from transfunctions.universal_namespace import UniversalNamespaceAroundFunction - some_global = 321 def test_set_something_and_get(): diff --git a/transfunctions/__init__.py b/transfunctions/__init__.py index 523d168..bb95671 100644 --- a/transfunctions/__init__.py +++ b/transfunctions/__init__.py @@ -1,18 +1,32 @@ -from transfunctions.decorators.transfunction import transfunction as transfunction from transfunctions.decorators.superfunction import superfunction as superfunction - -from transfunctions.markers import ( - async_context as async_context, - sync_context as sync_context, - generator_context as generator_context, - await_it as await_it, - yield_from_it as yield_from_it, -) - +from transfunctions.decorators.transfunction import transfunction as transfunction from transfunctions.errors import ( CallTransfunctionDirectlyError as CallTransfunctionDirectlyError, +) +from transfunctions.errors import ( DualUseOfDecoratorError as DualUseOfDecoratorError, +) +from transfunctions.errors import ( WrongDecoratorSyntaxError as WrongDecoratorSyntaxError, - WrongTransfunctionSyntaxError as WrongTransfunctionSyntaxError, +) +from transfunctions.errors import ( WrongMarkerSyntaxError as WrongMarkerSyntaxError, ) +from transfunctions.errors import ( + WrongTransfunctionSyntaxError as WrongTransfunctionSyntaxError, +) +from transfunctions.markers import ( + async_context as async_context, +) +from transfunctions.markers import ( + await_it as await_it, +) +from transfunctions.markers import ( + generator_context as generator_context, +) +from transfunctions.markers import ( + sync_context as sync_context, +) +from transfunctions.markers import ( + yield_from_it as yield_from_it, +) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index f0b8f67..6691455 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -2,14 +2,20 @@ from ast import AST, NodeTransformer, Return from functools import wraps from inspect import currentframe -from typing import Any, Dict, Generic, List, Optional, Union, Type, overload, cast from types import FrameType, TracebackType +from typing import Any, Dict, Generic, List, Optional, Type, Union, cast, overload from displayhooks import not_display from transfunctions.errors import WrongTransfunctionSyntaxError from transfunctions.transformer import FunctionTransformer -from transfunctions.typing import Callable, Coroutine, ReturnType, FunctionParams, Generator +from transfunctions.typing import ( + Callable, + Coroutine, + FunctionParams, + Generator, + ReturnType, +) class ParamSpecContainer(Generic[FunctionParams]): @@ -79,8 +85,7 @@ def sync_option( wrapped_coroutine.close() if not tilde_syntax: return transformer.get_usual_function()(*param_spec.args, **param_spec.kwargs) - else: - raise NotImplementedError(f'The tilde-syntax is enabled for the "{transformer.function.__name__}" function. Call it like this: ~{transformer.function.__name__}().') + raise NotImplementedError(f'The tilde-syntax is enabled for the "{transformer.function.__name__}" function. Call it like this: ~{transformer.function.__name__}().') return None @staticmethod @@ -99,7 +104,7 @@ def superfunction(function: Callable[FunctionParams, ReturnType]) -> Callable[Fu @overload def superfunction( - *, tilde_syntax: bool = True, check_decorators: bool = True + *, tilde_syntax: bool = True, check_decorators: bool = True, ) -> Callable[[Callable[FunctionParams, ReturnType]], Callable[FunctionParams, UsageTracer[FunctionParams, ReturnType]]]: ... @@ -128,7 +133,7 @@ def visit_Return(self, node: Return) -> Optional[Union[AST, List[AST]]]: def wrapper(*args: FunctionParams.args, **kwargs: FunctionParams.kwargs) -> UsageTracer[FunctionParams, ReturnType]: return UsageTracer(ParamSpecContainer(*args, **kwargs), transformer, tilde_syntax) - setattr(wrapper, "__is_superfunction__", True) + wrapper.__is_superfunction__ = True return wrapper diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index f19fb0e..71bd4e3 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -1,6 +1,6 @@ from inspect import currentframe -from typing import cast from types import FrameType +from typing import cast from transfunctions.transformer import FunctionTransformer from transfunctions.typing import Callable, FunctionParams, ReturnType diff --git a/transfunctions/markers.py b/transfunctions/markers.py index 0e42c81..b48d426 100644 --- a/transfunctions/markers.py +++ b/transfunctions/markers.py @@ -1,5 +1,5 @@ -from typing import Any, NoReturn, Generator from contextlib import contextmanager +from typing import Any, Generator, NoReturn from transfunctions.typing import IterableWithResults diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 9a319b2..5c0c9ee 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -7,24 +7,24 @@ Call, Constant, FunctionDef, - Module, Load, + Module, Name, NodeTransformer, Pass, Return, Store, With, + YieldFrom, arguments, increment_lineno, parse, - YieldFrom, ) from functools import update_wrapper, wraps from inspect import getfile, getsource, iscoroutinefunction, isfunction from sys import version_info -from types import FunctionType, MethodType, FrameType -from typing import Any, Dict, Generic, List, Optional, Union, Type, cast +from types import FrameType, FunctionType, MethodType +from typing import Any, Dict, Generic, List, Optional, Type, Union, cast from dill.source import getsource as dill_getsource # type: ignore[import-untyped] @@ -34,7 +34,14 @@ WrongDecoratorSyntaxError, WrongMarkerSyntaxError, ) -from transfunctions.typing import Coroutine, Callable, Generator, FunctionParams, ReturnType, SomeClassInstance +from transfunctions.typing import ( + Callable, + Coroutine, + FunctionParams, + Generator, + ReturnType, + SomeClassInstance, +) from transfunctions.universal_namespace import UniversalNamespaceAroundFunction @@ -213,10 +220,9 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] and check_decorators ): raise WrongDecoratorSyntaxError(f'The @{decorator_name} decorator cannot be used in conjunction with other decorators.') - else: - if transfunction_decorator is not None: - raise DualUseOfDecoratorError(f"You cannot use the @{decorator_name} decorator twice for the same function.") - transfunction_decorator = cast(Name, decorator) + if transfunction_decorator is not None: + raise DualUseOfDecoratorError(f"You cannot use the @{decorator_name} decorator twice for the same function.") + transfunction_decorator = cast(Name, decorator) node.decorator_list = [] return node diff --git a/transfunctions/typing.py b/transfunctions/typing.py index be3f573..21f7102 100644 --- a/transfunctions/typing.py +++ b/transfunctions/typing.py @@ -1,6 +1,6 @@ import sys -from typing import TypeVar from collections.abc import Iterable +from typing import TypeVar if sys.version_info >= (3, 10): from typing import ParamSpec @@ -27,4 +27,4 @@ else: IterableWithResults = Iterable # pragma: no cover -__all__ = ('ParamSpec', 'TypeAlias', 'Callable', 'Coroutine', 'Generator', 'ReturnType', 'FunctionParams', 'IterableWithResults', 'SomeClassInstance') +__all__ = ('Callable', 'Coroutine', 'FunctionParams', 'Generator', 'IterableWithResults', 'ParamSpec', 'ReturnType', 'SomeClassInstance', 'TypeAlias') diff --git a/transfunctions/universal_namespace.py b/transfunctions/universal_namespace.py index fb82ea5..a35dbac 100644 --- a/transfunctions/universal_namespace.py +++ b/transfunctions/universal_namespace.py @@ -1,6 +1,6 @@ -from typing import Dict, Optional, Any -from types import FrameType import builtins +from types import FrameType +from typing import Any, Dict, Optional from transfunctions.typing import Callable, FunctionParams, ReturnType From 08caaee523745345fb71f8a1cf7f8428b1865cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 01:35:16 +0300 Subject: [PATCH 75/86] Some lint issues --- tests/units/decorators/test_transfunction.py | 50 ++++++++++---------- transfunctions/__init__.py | 28 ++++++----- transfunctions/decorators/superfunction.py | 2 +- transfunctions/transformer.py | 16 +++---- transfunctions/typing.py | 1 + transfunctions/universal_namespace.py | 6 +-- 6 files changed, 54 insertions(+), 49 deletions(-) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 05fd4be..2f27149 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -651,12 +651,12 @@ def make(number): def test_write_global_variable_from_generator_function_without_arguments(): @transfunction def make(): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += 1 yield None - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_generator_function() list(function()) @@ -668,12 +668,12 @@ def make(): def test_write_global_variable_from_generator_function_with_arguments(): @transfunction def make(number): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += number yield None - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_generator_function() list(function(3)) @@ -1035,7 +1035,7 @@ def context_manager_with_parentnes(): @transfunction def template(): - with sync_context: + with sync_context: # noqa: SIM117 with context_manager_with_parentnes() as something: return something @@ -1051,7 +1051,7 @@ def context_manager_with_parentnes(): @transfunction def template(a, b): - with sync_context: + with sync_context: # noqa: SIM117 with context_manager_with_parentnes() as something: return something + a + b @@ -1067,7 +1067,7 @@ def context_manager_with_parentnes(c): @transfunction def template(): - with sync_context: + with sync_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: return something @@ -1083,7 +1083,7 @@ def context_manager_with_parentnes(c): @transfunction def template(a, b): - with sync_context: + with sync_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: return something + a + b @@ -1099,7 +1099,7 @@ def context_manager_with_parentnes(): @transfunction def template(): - with async_context: + with async_context: # noqa: SIM117 with context_manager_with_parentnes() as something: return something @@ -1115,7 +1115,7 @@ def context_manager_with_parentnes(): @transfunction def template(a, b): - with async_context: + with async_context: # noqa: SIM117 with context_manager_with_parentnes() as something: return something + a + b @@ -1131,7 +1131,7 @@ def context_manager_with_parentnes(c): @transfunction def template(): - with async_context: + with async_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: return something @@ -1147,7 +1147,7 @@ def context_manager_with_parentnes(c): @transfunction def template(a, b): - with async_context: + with async_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: return something + a + b @@ -1163,7 +1163,7 @@ def context_manager_with_parentnes(): @transfunction def template(): - with generator_context: + with generator_context: # noqa: SIM117 with context_manager_with_parentnes() as something: yield something @@ -1179,7 +1179,7 @@ def context_manager_with_parentnes(): @transfunction def template(a, b): - with generator_context: + with generator_context: # noqa: SIM117 with context_manager_with_parentnes() as something: yield something + a + b @@ -1195,7 +1195,7 @@ def context_manager_with_parentnes(c): @transfunction def template(): - with generator_context: + with generator_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: yield something @@ -1211,7 +1211,7 @@ def context_manager_with_parentnes(c): @transfunction def template(a, b): - with generator_context: + with generator_context: # noqa: SIM117 with context_manager_with_parentnes(4) as something: yield something + a + b @@ -1333,7 +1333,7 @@ def template(number=123): def test_list_literal_default_value_for_usual_function(): @transfunction - def template(number, lst=[]): + def template(number, lst=[]): # noqa: B006 lst.append(number) return lst @@ -1345,7 +1345,7 @@ def template(number, lst=[]): def test_list_literal_default_value_it_the_same_for_all_types_of_functions(): @transfunction - def template(number, lst=[]): + def template(number, lst=[]): # noqa: B006 lst.append(number) with async_context: return lst @@ -1392,7 +1392,7 @@ def template(number=123): def test_list_literal_default_value_for_async_function(): @transfunction - def template(number, lst=[]): + def template(number, lst=[]): # noqa: B006 lst.append(number) return lst @@ -1424,7 +1424,7 @@ def template(number=123): def test_list_literal_default_value_for_generator_function(): @transfunction - def template(number, lst=[]): + def template(number, lst=[]): # noqa: B006 lst.append(number) yield from lst @@ -1463,7 +1463,7 @@ def template(number=SOME_GLOBAL): def test_resetted_global_variable_default_value_for_usual_function(): container = [] - SOME_GLOBAL = 'kek' + SOME_GLOBAL = 'kek' # noqa: N806 @transfunction def template(number=SOME_GLOBAL): @@ -1498,7 +1498,7 @@ def template(number=SOME_GLOBAL): def test_resetted_global_variable_default_value_for_async_function(): - SOME_GLOBAL = 'kek' + SOME_GLOBAL = 'kek' # noqa: N806 @transfunction def template(number=SOME_GLOBAL): @@ -1532,7 +1532,7 @@ def template(number=SOME_GLOBAL): def test_resetted_global_variable_default_value_for_generator_function(): - SOME_GLOBAL = 'kek' + SOME_GLOBAL = 'kek' # noqa: N806 @transfunction def template(number=SOME_GLOBAL): diff --git a/transfunctions/__init__.py b/transfunctions/__init__.py index bb95671..3edddc6 100644 --- a/transfunctions/__init__.py +++ b/transfunctions/__init__.py @@ -1,32 +1,36 @@ -from transfunctions.decorators.superfunction import superfunction as superfunction -from transfunctions.decorators.transfunction import transfunction as transfunction +from transfunctions.decorators.superfunction import ( + superfunction as superfunction, # noqa: PLC0414 +) +from transfunctions.decorators.transfunction import ( + transfunction as transfunction, # noqa: PLC0414 +) from transfunctions.errors import ( - CallTransfunctionDirectlyError as CallTransfunctionDirectlyError, + CallTransfunctionDirectlyError as CallTransfunctionDirectlyError, # noqa: PLC0414 ) from transfunctions.errors import ( - DualUseOfDecoratorError as DualUseOfDecoratorError, + DualUseOfDecoratorError as DualUseOfDecoratorError, # noqa: PLC0414 ) from transfunctions.errors import ( - WrongDecoratorSyntaxError as WrongDecoratorSyntaxError, + WrongDecoratorSyntaxError as WrongDecoratorSyntaxError, # noqa: PLC0414 ) from transfunctions.errors import ( - WrongMarkerSyntaxError as WrongMarkerSyntaxError, + WrongMarkerSyntaxError as WrongMarkerSyntaxError, # noqa: PLC0414 ) from transfunctions.errors import ( - WrongTransfunctionSyntaxError as WrongTransfunctionSyntaxError, + WrongTransfunctionSyntaxError as WrongTransfunctionSyntaxError, # noqa: PLC0414 ) from transfunctions.markers import ( - async_context as async_context, + async_context as async_context, # noqa: PLC0414 ) from transfunctions.markers import ( - await_it as await_it, + await_it as await_it, # noqa: PLC0414 ) from transfunctions.markers import ( - generator_context as generator_context, + generator_context as generator_context, # noqa: PLC0414 ) from transfunctions.markers import ( - sync_context as sync_context, + sync_context as sync_context, # noqa: PLC0414 ) from transfunctions.markers import ( - yield_from_it as yield_from_it, + yield_from_it as yield_from_it, # noqa: PLC0414 ) diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index 6691455..767ce10 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -125,7 +125,7 @@ def decorator(function: Callable[FunctionParams, ReturnType]) -> Callable[Functi if not tilde_syntax: class NoReturns(NodeTransformer): - def visit_Return(self, node: Return) -> Optional[Union[AST, List[AST]]]: + def visit_Return(self, node: Return) -> Optional[Union[AST, List[AST]]]: # noqa: ARG002, N802 raise WrongTransfunctionSyntaxError('A superfunction cannot contain a return statement.') transformer.get_usual_function(addictional_transformers=[NoReturns()]) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 5c0c9ee..366162d 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -66,7 +66,7 @@ def __init__( self.base_object: Optional[SomeClassInstance] = None # type: ignore[valid-type] self.cache: Dict[str, Callable[FunctionParams, ReturnType]] = {} - def __call__(self, *args: Any, **kwargs: Any) -> None: + def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: ARG002 raise CallTransfunctionDirectlyError("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.") def __get__( @@ -90,7 +90,7 @@ def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, Ret original_function = self.function class ConvertSyncFunctionToAsync(NodeTransformer): - def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: + def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: # noqa: N802 if node.name == original_function.__name__: return AsyncFunctionDef( # type: ignore[no-any-return, call-overload] name=original_function.__name__, @@ -105,7 +105,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFuncti return node class ExtractAwaitExpressions(NodeTransformer): - def visit_Call(self, node: Call) -> Union[Call, Await]: + def visit_Call(self, node: Call) -> Union[Call, Await]: # noqa: N802 if isinstance(node.func, Name) and node.func.id == 'await_it': if len(node.args) != 1 or node.keywords: raise WrongMarkerSyntaxError('The "await_it" marker can be used with only one positional argument.') @@ -132,7 +132,7 @@ def visit_Call(self, node: Call) -> Union[Call, Await]: def get_generator_function(self) -> Callable[FunctionParams, Generator[ReturnType, None, None]]: class ConvertYieldFroms(NodeTransformer): - def visit_Call(self, node: Call) -> Optional[Union[AST, List[AST]]]: + def visit_Call(self, node: Call) -> Optional[Union[AST, List[AST]]]: # noqa: N802 if isinstance(node.func, Name) and node.func.id == 'yield_from_it': if len(node.args) != 1 or node.keywords: raise WrongMarkerSyntaxError('The "yield_from_it" marker can be used with only one positional argument.') @@ -188,7 +188,7 @@ def extract_context(self, context_name: str, addictional_transformers: Optional[ check_decorators = self.check_decorators class RewriteContexts(NodeTransformer): - def visit_With(self, node: With) -> Optional[Union[AST, List[AST]]]: + def visit_With(self, node: With) -> Optional[Union[AST, List[AST]]]: # noqa: N802 if len(node.items) == 1: if isinstance(node.items[0].context_expr, Name): context_expr = node.items[0].context_expr @@ -202,7 +202,7 @@ def visit_With(self, node: With) -> Optional[Union[AST, List[AST]]]: return node class DeleteDecorator(NodeTransformer): - def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]]]: + def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]]]: # noqa: N802 if node.name == original_function.__name__: nonlocal transfunction_decorator transfunction_decorator = None @@ -212,7 +212,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] for decorator in node.decorator_list: if isinstance(decorator, Call): - decorator = decorator.func + decorator = decorator.func # noqa: PLW2901 if ( isinstance(decorator, Name) @@ -244,7 +244,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] tree = self.wrap_ast_by_closures(tree) - if version_info.minor > 10: + if version_info > (3, 10): increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno)) else: increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno - 1)) diff --git a/transfunctions/typing.py b/transfunctions/typing.py index 21f7102..695986b 100644 --- a/transfunctions/typing.py +++ b/transfunctions/typing.py @@ -1,3 +1,4 @@ +# noqa: A005 import sys from collections.abc import Iterable from typing import TypeVar diff --git a/transfunctions/universal_namespace.py b/transfunctions/universal_namespace.py index a35dbac..cbebc71 100644 --- a/transfunctions/universal_namespace.py +++ b/transfunctions/universal_namespace.py @@ -21,9 +21,9 @@ def __getitem__(self, key: str) -> Any: frame = self.frame while frame: - locals = frame.f_locals - if key in locals: - return locals[key] + locals_from_frame = frame.f_locals + if key in locals_from_frame: + return locals_from_frame[key] frame = frame.f_back if key in self.function.__globals__: From 382b83289e6307aa613c00a5358d8973ebf931d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 02:00:49 +0300 Subject: [PATCH 76/86] Lint issues --- pyproject.toml | 2 +- requirements_dev.txt | 2 +- tests/units/decorators/test_superfunction.py | 111 ++++++++----------- tests/units/decorators/test_transfunction.py | 74 ++++++------- 4 files changed, 87 insertions(+), 102 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fed1a33..04e2d19 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ runner = "pytest" markers = ["mypy_testing"] [tool.ruff] -lint.ignore = ['E501', 'E712', 'PTH123', 'PTH118', 'PLR2004', 'PTH107', 'SIM105', 'SIM102', 'RET503', 'PLR0912', 'C901'] +lint.ignore = ['E501', 'E712', 'PTH123', 'PTH118', 'PLR2004', 'PTH107', 'SIM105', 'SIM102', 'RET503', 'PLR0912', 'C901', 'RUF001'] lint.select = ["ERA001", "YTT", "ASYNC", "BLE", "B", "A", "COM", "INP", "PIE", "T20", "PT", "RSE", "RET", "SIM", "SLOT", "TID252", "ARG", "PTH", "I", "C90", "N", "E", "W", "D201", "D202", "D419", "F", "PL", "PLE", "PLR", "PLW", "RUF", "TRY201", "TRY400", "TRY401"] format.quote-style = "single" diff --git a/requirements_dev.txt b/requirements_dev.txt index d097412..2dd4729 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -6,4 +6,4 @@ mypy==1.14.1 pytest-mypy-testing==0.1.3 ruff==0.9.9 mutmut==3.2.3 -full_match==0.0.2 +full_match==0.0.3 diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index a41f710..073252a 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -3,8 +3,8 @@ from asyncio import run from contextlib import redirect_stdout -import full_match import pytest +from full_match import match from transfunctions import ( WrongDecoratorSyntaxError, @@ -39,9 +39,9 @@ def test_just_sync_call_without_breackets(): @superfunction def function(): with sync_context: - print(1) + print(1) # noqa: T201 with async_context: - print(2) + print(2) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -55,9 +55,9 @@ def test_just_sync_call_without_tilde_syntax(): @superfunction(tilde_syntax=False) def function(): with sync_context: - print(1) + print(1) # noqa: T201 with async_context: - print(2) + print(2) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -71,9 +71,9 @@ def test_just_sync_call_with_tilde_syntax(): @superfunction(tilde_syntax=True) def function(): with sync_context: - print(1) + print(1) # noqa: T201 with async_context: - print(2) + print(2) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -87,9 +87,9 @@ def test_just_async_call(): @superfunction def function(): with sync_context: - print(1) + print(1) # noqa: T201 with async_context: - print(2) + print(2) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -103,9 +103,9 @@ def test_just_generator_iteration(): @superfunction def function(): with sync_context: - print(1) + print(1) # noqa: T201 with async_context: - print(2) + print(2) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -121,9 +121,9 @@ def test_just_sync_call_with_arguments(): @superfunction def function(a, b): with sync_context: - print(a) + print(a) # noqa: T201 with async_context: - print(b) + print(b) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -137,9 +137,9 @@ def test_just_async_call_with_arguments(): @superfunction def function(a, b): with sync_context: - print(a) + print(a) # noqa: T201 with async_context: - print(b) + print(b) # noqa: T201 with generator_context: yield from [1, 2, 3] @@ -153,9 +153,9 @@ def test_just_generator_with_arguments_iteration(): @superfunction def function(a, b): with sync_context: - print(a) + print(a) # noqa: T201 with async_context: - print(b) + print(b) # noqa: T201 with generator_context: yield from [a, b, 3] @@ -186,18 +186,18 @@ def function(a, b, c=4, d=3): def test_tilda_syntax_for_function_call_without_arguments_raise_exception(): @superfunction def function(): - raise ValueError + raise ValueError('some text') - with pytest.raises(ValueError): - ~function() == 124 + with pytest.raises(ValueError, match=match('some text')): + ~function() def test_tilda_syntax_for_function_call_with_arguments_raise_exception(): @superfunction - def function(a, b, c=4, d=3): - raise ValueError + def function(a, b, c=4, d=3): # noqa: ARG001 + raise ValueError('some text') - with pytest.raises(ValueError): + with pytest.raises(ValueError, match=match('some text')): ~function(2, 3, d=5) @@ -278,7 +278,7 @@ def other_decorator(function): def template(): pass - with pytest.raises(WrongDecoratorSyntaxError, match=full_match('The @superfunction decorator cannot be used in conjunction with other decorators.')): + with pytest.raises(WrongDecoratorSyntaxError, match=match('The @superfunction decorator cannot be used in conjunction with other decorators.')): ~template() @@ -291,24 +291,24 @@ def other_decorator(function): def template(): pass - with pytest.raises(WrongDecoratorSyntaxError, match=full_match('The @superfunction decorator cannot be used in conjunction with other decorators.')): + with pytest.raises(WrongDecoratorSyntaxError, match=match('The @superfunction decorator cannot be used in conjunction with other decorators.')): ~template() def test_pass_coroutine_function_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @superfunction. You can't use async functions.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @superfunction. You can't use async functions.")): @superfunction async def function_maker(): return 4 def test_pass_not_function_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @superfunction.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @superfunction.")): superfunction(1) def test_try_to_pass_lambda_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @superfunction. Don't use lambdas here.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @superfunction. Don't use lambdas here.")): superfunction(lambda x: x) @@ -317,7 +317,7 @@ def test_choose_tilde_syntax_off_and_use_tilde(): def function(): pass - with pytest.raises(NotImplementedError, match=full_match('The syntax with ~ is disabled for this superfunction. Call it with simple breackets.')): + with pytest.raises(NotImplementedError, match=match('The syntax with ~ is disabled for this superfunction. Call it with simple breackets.')): ~function() @@ -360,21 +360,21 @@ def function(): def test_there_is_exception_if_not_tilde_mode_and_in_function_is_empty_return_in_common_block(): - with pytest.raises(WrongTransfunctionSyntaxError, match=full_match('A superfunction cannot contain a return statement.')): + with pytest.raises(WrongTransfunctionSyntaxError, match=match('A superfunction cannot contain a return statement.')): @superfunction(tilde_syntax=False) def function(): return def test_there_is_exception_if_not_tilde_mode_and_in_function_is_return_true_in_common_block(): - with pytest.raises(WrongTransfunctionSyntaxError, match=full_match('A superfunction cannot contain a return statement.')): + with pytest.raises(WrongTransfunctionSyntaxError, match=match('A superfunction cannot contain a return statement.')): @superfunction(tilde_syntax=False) def function(): return True def test_there_is_exception_if_not_tilde_mode_and_in_function_is_empty_return_in_sync_block(): - with pytest.raises(WrongTransfunctionSyntaxError, match=full_match('A superfunction cannot contain a return statement.')): + with pytest.raises(WrongTransfunctionSyntaxError, match=match('A superfunction cannot contain a return statement.')): @superfunction(tilde_syntax=False) def function(): with sync_context: @@ -382,7 +382,7 @@ def function(): def test_there_is_exception_if_not_tilde_mode_and_in_function_is_return_true_in_sync_block(): - with pytest.raises(WrongTransfunctionSyntaxError, match=full_match('A superfunction cannot contain a return statement.')): + with pytest.raises(WrongTransfunctionSyntaxError, match=match('A superfunction cannot contain a return statement.')): @superfunction(tilde_syntax=False) def function(): with sync_context: @@ -475,19 +475,6 @@ def function(): assert list(function()) == [1, 2, 3] - - - - - - - - - - - - - def test_await_it_with_two_arguments(): async def another_function(): return None @@ -497,7 +484,7 @@ def template(): with async_context: return await_it(another_function(), another_function()) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): run(template()) @@ -507,7 +494,7 @@ def template(): with async_context: return await_it() - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): run(template()) @@ -520,7 +507,7 @@ def template(): with async_context: return await_it(another_function(), kek=another_function()) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): run(template()) @@ -530,7 +517,7 @@ def template(): with generator_context: return yield_from_it([1, 2, 3], [1, 2, 3]) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): list(template()) @@ -540,7 +527,7 @@ def template(): with generator_context: return yield_from_it() - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): list(template()) @@ -550,7 +537,7 @@ def template(): with generator_context: return yield_from_it([1, 2, 3], kek=[1, 2, 3]) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): list(template()) @@ -572,7 +559,7 @@ def function(number=123): def test_list_literal_default_value_for_usual_function_with_tilde(): @superfunction - def function(number, lst=[]): + def function(number, lst=[]): # noqa: B006 lst.append(number) return lst @@ -582,7 +569,7 @@ def function(number, lst=[]): def test_list_literal_default_value_it_the_same_for_all_types_of_functions_when_usual_one_is_with_tilde(): @superfunction - def function(number, lst=[]): + def function(number, lst=[]): # noqa: B006 lst.append(number) with async_context: return lst @@ -619,7 +606,7 @@ def function(number=123): def test_list_literal_default_value_for_async_function(): @superfunction - def function(number, lst=[]): + def function(number, lst=[]): # noqa: B006 lst.append(number) return lst @@ -645,7 +632,7 @@ def function(number=123): def test_list_literal_default_value_for_generator_function(): @superfunction - def function(number, lst=[]): + def function(number, lst=[]): # noqa: B006 lst.append(number) yield from lst @@ -779,15 +766,13 @@ def test_use_decorator_without_at(): def template(): pass - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): - function = superfunction(template) - ~function() + function = superfunction(template) + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + ~function() - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): - function = superfunction(template) + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): run(function()) - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): - function = superfunction(template) + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @superfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): list(function()) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 2f27149..d70031e 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -3,8 +3,8 @@ from contextlib import contextmanager from inspect import getsourcelines, iscoroutinefunction, isfunction, isgeneratorfunction -import full_match import pytest +from full_match import match from transfunctions import ( CallTransfunctionDirectlyError, @@ -104,7 +104,7 @@ def function(): @pytest.mark.parametrize( - ['args', 'kwargs'], + ('args', 'kwargs'), [ ((), {}), (('lol', 'kek'), {}), @@ -117,19 +117,19 @@ def test_direct_call_or_transformer(args, kwargs): def function_maker(*args, **kwargs): pass - with pytest.raises(CallTransfunctionDirectlyError, match=full_match("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.")): + with pytest.raises(CallTransfunctionDirectlyError, match=match("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.")): function_maker(*args, **kwargs) def test_pass_coroutine_function_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @transfunction. You can't use async functions.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @transfunction. You can't use async functions.")): @transfunction async def function_maker(): return 4 def test_pass_not_function_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @transfunction.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @transfunction.")): transfunction(1) @@ -196,7 +196,7 @@ def function_maker(a, b, c=3): def test_try_to_pass_lambda_to_decorator(): - with pytest.raises(ValueError, match=full_match("Only regular or generator functions can be used as a template for @transfunction. Don't use lambdas here.")): + with pytest.raises(ValueError, match=match("Only regular or generator functions can be used as a template for @transfunction. Don't use lambdas here.")): transfunction(lambda x: x) @@ -235,7 +235,7 @@ def make(): try: function() - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -252,7 +252,7 @@ def make(): try: run(function()) - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -270,7 +270,7 @@ def make(): try: [x for x in function()] - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -288,7 +288,7 @@ def make(): try: function() - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -306,7 +306,7 @@ def make(): try: run(function()) - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -325,7 +325,7 @@ def make(): try: [x for x in function()] - assert False + raise AssertionError except ValueError as e: certain_traceback = list(traceback.extract_tb(e.__traceback__)) @@ -341,12 +341,12 @@ def function(): make = transfunction(function) - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): function = make.get_generator_function() def test_double_use_of_decorator(): - with pytest.raises(DualUseOfDecoratorError, match=full_match("You cannot use the 'transfunction' decorator twice for the same function.")): + with pytest.raises(DualUseOfDecoratorError, match=match("You cannot use the 'transfunction' decorator twice for the same function.")): @transfunction @transfunction def make(): @@ -587,11 +587,11 @@ def make(number): def test_write_global_variable_from_usual_function_without_arguments(): @transfunction def make(): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += 1 - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_usual_function() function() @@ -603,11 +603,11 @@ def make(): def test_write_global_variable_from_usual_function_with_arguments(): @transfunction def make(number): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += number - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_usual_function() function(3) @@ -619,11 +619,11 @@ def make(number): def test_write_global_variable_from_async_function_without_arguments(): @transfunction def make(): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += 1 - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_async_function() run(function()) @@ -635,11 +635,11 @@ def make(): def test_write_global_variable_from_async_function_with_arguments(): @transfunction def make(number): - global SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 SOME_GLOBAL += number - global SOME_GLOBAL - SOME_GLOBAL_BEFORE = SOME_GLOBAL + global SOME_GLOBAL # noqa: PLW0603 + SOME_GLOBAL_BEFORE = SOME_GLOBAL # noqa: N806 function = make.get_async_function() run(function(3)) @@ -787,7 +787,7 @@ def other_decorator(function): def template(): pass - with pytest.raises(WrongDecoratorSyntaxError, match=full_match('The @transfunction decorator cannot be used in conjunction with other decorators.')): + with pytest.raises(WrongDecoratorSyntaxError, match=match('The @transfunction decorator cannot be used in conjunction with other decorators.')): template.get_usual_function() @@ -800,7 +800,7 @@ def other_decorator(function): def template(): pass - with pytest.raises(WrongDecoratorSyntaxError, match=full_match('The @transfunction decorator cannot be used in conjunction with other decorators.')): + with pytest.raises(WrongDecoratorSyntaxError, match=match('The @transfunction decorator cannot be used in conjunction with other decorators.')): template.get_usual_function() @@ -1254,7 +1254,7 @@ def template(): with async_context: return await_it(another_function(), another_function()) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): template.get_async_function() @@ -1264,7 +1264,7 @@ def template(): with async_context: return await_it() - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): template.get_async_function() @@ -1277,7 +1277,7 @@ def template(): with async_context: return await_it(another_function(), kek=another_function()) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "await_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "await_it" marker can be used with only one positional argument.')): template.get_async_function() @@ -1287,7 +1287,7 @@ def template(): with generator_context: return yield_from_it([1, 2, 3], [1, 2, 3]) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): template.get_generator_function() @@ -1297,7 +1297,7 @@ def template(): with generator_context: return yield_from_it() - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): template.get_generator_function() @@ -1307,7 +1307,7 @@ def template(): with generator_context: return yield_from_it([1, 2, 3], kek=[1, 2, 3]) - with pytest.raises(WrongMarkerSyntaxError, match=full_match('The "yield_from_it" marker can be used with only one positional argument.')): + with pytest.raises(WrongMarkerSyntaxError, match=match('The "yield_from_it" marker can be used with only one positional argument.')): template.get_generator_function() @@ -1549,11 +1549,11 @@ def template(): template = transfunction(template) - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): template.get_usual_function() - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): template.get_async_function() - with pytest.raises(WrongDecoratorSyntaxError, match=full_match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): + with pytest.raises(WrongDecoratorSyntaxError, match=match("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")): template.get_generator_function() From b662ae6c4a072d9822cedd9be8eff5be32596470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 02:06:16 +0300 Subject: [PATCH 77/86] Lint issues --- tests/documentation/test_readme.py | 8 ++--- tests/typing/__init__.py | 1 + .../decorators/test_superfunction_typing.py | 36 +++++++++---------- .../decorators/test_transfunction_typing.py | 28 +++++++-------- transfunctions/decorators/superfunction.py | 2 +- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/tests/documentation/test_readme.py b/tests/documentation/test_readme.py index 631e752..221796f 100644 --- a/tests/documentation/test_readme.py +++ b/tests/documentation/test_readme.py @@ -13,13 +13,13 @@ def test_quick_start(): @transfunction def template(): - print('so, ', end='') + print('so, ', end='') # noqa: T201 with sync_context: - print("it's just usual function!") + print("it's just usual function!") # noqa: T201 with async_context: - print("it's an async function!") + print("it's an async function!") # noqa: T201 with generator_context: - print("it's a generator function!") + print("it's a generator function!") # noqa: T201 yield buffer = io.StringIO() diff --git a/tests/typing/__init__.py b/tests/typing/__init__.py index e69de29..dc9fd4c 100644 --- a/tests/typing/__init__.py +++ b/tests/typing/__init__.py @@ -0,0 +1 @@ +# noqa: A005 diff --git a/tests/typing/decorators/test_superfunction_typing.py b/tests/typing/decorators/test_superfunction_typing.py index cab1b29..4e07e44 100644 --- a/tests/typing/decorators/test_superfunction_typing.py +++ b/tests/typing/decorators/test_superfunction_typing.py @@ -31,7 +31,7 @@ @pytest.mark.mypy_testing def test_superfunction_deduced_return_type_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -43,7 +43,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_deduced_return_type_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -55,7 +55,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_incorrect_arg_type_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -67,7 +67,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_incorrect_kwarg_type_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -79,7 +79,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_on_correct_args_types_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -91,7 +91,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_incorrect_arg_type_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -103,7 +103,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_incorrect_kwarg_type_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -115,7 +115,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_on_correct_args_types_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -127,7 +127,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_missing_args_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -141,7 +141,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_superfunction_param_spec_fail_on_extra_args_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -155,7 +155,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_superfunction_param_spec_fail_on_extra_kwargs_sync() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -168,7 +168,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_superfunction_param_spec_fail_on_missing_args_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -182,7 +182,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_superfunction_param_spec_fail_on_extra_args_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -196,7 +196,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_superfunction_param_spec_fail_on_extra_kwargs_async() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -210,7 +210,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail # it shouldn't work because typed_superfunction is a generator function, gut it's not returning a generator object according to it's typing. def test_simple_using_of_generator_function_with_simple_yield_from() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -226,7 +226,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_wrong_using_of_generator_function_with_simple_yield_from() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -240,7 +240,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_simple_using_of_generator_function_with_yield_from_it_marker_function() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -256,7 +256,7 @@ def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_using_of_generator_function_with_yield_from_it_marker_function_with_wrong_return_value() -> None: @superfunction - def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_superfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: diff --git a/tests/typing/decorators/test_transfunction_typing.py b/tests/typing/decorators/test_transfunction_typing.py index ec88800..f49d297 100644 --- a/tests/typing/decorators/test_transfunction_typing.py +++ b/tests/typing/decorators/test_transfunction_typing.py @@ -15,7 +15,7 @@ @pytest.mark.mypy_testing def test_transfunction_deduced_return_type_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -28,7 +28,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_deduced_return_type_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -40,7 +40,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_incorrect_arg_type_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -52,7 +52,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_incorrect_kwarg_type_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -64,7 +64,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_missing_args_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -78,7 +78,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_transfunction_param_spec_fail_on_extra_args_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -92,7 +92,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_transfunction_param_spec_fail_on_extra_kwargs_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -105,7 +105,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_on_correct_args_types_sync(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -117,7 +117,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_incorrect_arg_type_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -129,7 +129,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_incorrect_kwarg_type_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -141,7 +141,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.mypy_testing def test_transfunction_param_spec_fail_on_missing_args_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -154,7 +154,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_transfunction_param_spec_fail_on_extra_args_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -168,7 +168,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_transfunction_param_spec_fail_on_extra_kwargs_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: @@ -182,7 +182,7 @@ def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: @pytest.mark.xfail def test_transfunction_param_spec_on_correct_args_types_async(): @transfunction - def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: + def typed_transfunction(arg: float, *, kwarg: int = 0) -> int: # noqa: ARG001 with sync_context: return 1 with async_context: diff --git a/transfunctions/decorators/superfunction.py b/transfunctions/decorators/superfunction.py index 767ce10..1d53608 100644 --- a/transfunctions/decorators/superfunction.py +++ b/transfunctions/decorators/superfunction.py @@ -133,7 +133,7 @@ def visit_Return(self, node: Return) -> Optional[Union[AST, List[AST]]]: # noqa def wrapper(*args: FunctionParams.args, **kwargs: FunctionParams.kwargs) -> UsageTracer[FunctionParams, ReturnType]: return UsageTracer(ParamSpecContainer(*args, **kwargs), transformer, tilde_syntax) - wrapper.__is_superfunction__ = True + wrapper.__is_superfunction__ = True # type: ignore[attr-defined] return wrapper From 76fb2f4a5cb3abd4c713c67106e2e3f334f9e6e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 02:10:23 +0300 Subject: [PATCH 78/86] Add venv3 to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c4d5adc..a9fafeb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ test.py dist venv .venv +venv3 build .ruff_cache .mypy_cache From d0fb1aa45287286e72078f653e0faca1a0560e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 02:44:44 +0300 Subject: [PATCH 79/86] Fix version check for Python 3.10+ compatibility --- transfunctions/transformer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 366162d..45be889 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -207,7 +207,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] nonlocal transfunction_decorator transfunction_decorator = None - if not node.decorator_list and check_decorators: + if (not node.decorator_list) and check_decorators: raise WrongDecoratorSyntaxError(f"The @{decorator_name} decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.") for decorator in node.decorator_list: @@ -244,7 +244,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] tree = self.wrap_ast_by_closures(tree) - if version_info > (3, 10): + if version_info.minor > 10: increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno)) else: increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno - 1)) From 9fc6d9e9af30a98a39a0845cb666f3f8b2210394 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 02:45:20 +0300 Subject: [PATCH 80/86] Lint ignore --- transfunctions/transformer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index 45be889..c0ddf1a 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -244,7 +244,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] tree = self.wrap_ast_by_closures(tree) - if version_info.minor > 10: + if version_info.minor > 10: # noqa: YTT204 increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno)) else: increment_lineno(tree, n=(self.decorator_lineno - cast(Name, transfunction_decorator).lineno - 1)) From 874ea833f4302c85471ef9d603f46ed3ccad8465 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 03:06:58 +0300 Subject: [PATCH 81/86] Add usage note for yield_from_it replacement --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 75f16c0..a451155 100644 --- a/README.md +++ b/README.md @@ -310,3 +310,22 @@ There are 2 main difficulties in developing typing here: - We mix several types of syntax in a single template function, but the static analyzer does not know that this is a template and part of the code will be deleted from here. In its opinion, this is the final function that will continue to be used in your project. As you can see, typing in Python is not well suited for metaprogramming. However, in this project, almost all the problems with typing turned out to be solved in one way or another. The main reason why this is so is that we mostly *remove* code from functions, but hardly *add* it there during code generation. In other words, we almost never encounter the problem of how to type the *added* code. This makes the solution to most typing problems accessible. However! Unfortunately, we were not able to completely hide all the typing problems under the hood, but you should still be aware of some of them if you use `mypy` or another analyzer. + +If you use the keyword `yield from`, you need to call the function `yield_from_it` instead: + +```python +from transfunctions import yield_it + +@superfunction +def my_superfunction(): + print('so, ', end='') + with sync_context: + print("it's just usual function!") + with async_context: + print("it's an async function!") + with generator_context: + print("it's a generator function!") + yield_from_it([1, 2, 3]) +``` + +The keywords yield or yield from are available to you and work perfectly, but from the point of view of a static type checker, they turn the function into a generator, which should also mean a special type annotation. By replacing this fragment with a function call, we hack it. From 31c7a8de9654d1c74323bd883ae539ba80707d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 18:02:49 +0300 Subject: [PATCH 82/86] Hide badges in readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a451155..8ecd4a9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -![logo](https://raw.githubusercontent.com/pomponchik/transfunctions/develop/docs/assets/logo_2.svg) +
+ [![Downloads](https://static.pepy.tech/badge/transfunctions/month)](https://pepy.tech/project/transfunctions) [![Downloads](https://static.pepy.tech/badge/transfunctions)](https://pepy.tech/project/transfunctions) @@ -11,6 +12,10 @@ [![Checked with mypy](http://www.mypy-lang.org/static/mypy_badge.svg)](http://mypy-lang.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) +
+ +![logo](https://raw.githubusercontent.com/pomponchik/transfunctions/develop/docs/assets/logo_2.svg) + This library is designed to solve one of the most important problems in python programming - dividing all written code into 2 camps: sync and async. We get rid of code duplication by using templates. From 7715c63256fde39185970e9f81b27883d0a1e0b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 18:15:28 +0300 Subject: [PATCH 83/86] Refactor transfunction decorator to support variadic args --- transfunctions/decorators/transfunction.py | 30 ++++++++++++++-------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index 71bd4e3..3ecb106 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -1,18 +1,28 @@ from inspect import currentframe from types import FrameType -from typing import cast +from typing import Union, cast from transfunctions.transformer import FunctionTransformer from transfunctions.typing import Callable, FunctionParams, ReturnType def transfunction( - function: Callable[FunctionParams, ReturnType], -) -> FunctionTransformer[FunctionParams, ReturnType]: - return FunctionTransformer( - function, - cast(FrameType, cast(FrameType, currentframe()).f_back).f_lineno, - "transfunction", - cast(FrameType, cast(FrameType, currentframe()).f_back), - True, - ) + *args: Callable[FunctionParams, ReturnType], check_decorators: bool = True, +) -> Union[Callable[[Callable[FunctionParams, ReturnType]], FunctionTransformer[FunctionParams, ReturnType]], FunctionTransformer[FunctionParams, ReturnType]]: + frame = currentframe() + + def decorator( + function: Callable[FunctionParams, ReturnType], + ) -> FunctionTransformer[FunctionParams, ReturnType]: + return FunctionTransformer( + function, + cast(FrameType, cast(FrameType, frame).f_back).f_lineno, + "transfunction", + cast(FrameType, cast(FrameType, frame).f_back), + check_decorators, + ) + + if args: + return decorator(args[0]) + + return decorator From 7fae3499ec42322927c065eb88fdf499cd70d402 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 18:18:42 +0300 Subject: [PATCH 84/86] Mypy strict mode --- .github/workflows/lint.yml | 2 +- transfunctions/transformer.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 74dccac..502743a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -43,7 +43,7 @@ jobs: - name: Run mypy shell: bash - run: mypy transfunctions + run: mypy --strict transfunctions - name: Run mypy for tests shell: bash diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index c0ddf1a..b3677b5 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -92,7 +92,7 @@ def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, Ret class ConvertSyncFunctionToAsync(NodeTransformer): def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: # noqa: N802 if node.name == original_function.__name__: - return AsyncFunctionDef( # type: ignore[no-any-return, call-overload] + return AsyncFunctionDef( name=original_function.__name__, args=node.args, body=node.body, @@ -270,7 +270,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] def wrap_ast_by_closures(self, tree: Module) -> Module: old_functiondef = tree.body[0] - tree.body[0] = FunctionDef( # type: ignore[call-overload] + tree.body[0] = FunctionDef( name='wrapper', body=[Assign(targets=[Name(id=name, ctx=Store(), col_offset=0)], value=Constant(value=None, col_offset=0), col_offset=0) for name in self.function.__code__.co_freevars] + [ old_functiondef, From d27729d61ac5d5c2577ec1c10a6da092249ca215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 18:20:41 +0300 Subject: [PATCH 85/86] Add type ignore comments to suppress type checker warnings in AST transformer --- transfunctions/transformer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index b3677b5..7c8e744 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -92,7 +92,7 @@ def get_async_function(self) -> Callable[FunctionParams, Coroutine[Any, Any, Ret class ConvertSyncFunctionToAsync(NodeTransformer): def visit_FunctionDef(self, node: FunctionDef) -> Union[FunctionDef, AsyncFunctionDef]: # noqa: N802 if node.name == original_function.__name__: - return AsyncFunctionDef( + return AsyncFunctionDef( # type: ignore[no-any-return, call-overload, unused-ignore] name=original_function.__name__, args=node.args, body=node.body, @@ -270,7 +270,7 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] def wrap_ast_by_closures(self, tree: Module) -> Module: old_functiondef = tree.body[0] - tree.body[0] = FunctionDef( + tree.body[0] = FunctionDef( # type: ignore[call-overload, unused-ignore] name='wrapper', body=[Assign(targets=[Name(id=name, ctx=Store(), col_offset=0)], value=Constant(value=None, col_offset=0), col_offset=0) for name in self.function.__code__.co_freevars] + [ old_functiondef, From 9494a1973893b932138880f623c2d65c453e464f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=95=D0=B2=D0=B3=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=91=D0=BB?= =?UTF-8?q?=D0=B8=D0=BD=D0=BE=D0=B2?= Date: Tue, 10 Feb 2026 18:28:23 +0300 Subject: [PATCH 86/86] Add overload definitions for transfunction decorator --- transfunctions/decorators/transfunction.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/transfunctions/decorators/transfunction.py b/transfunctions/decorators/transfunction.py index 3ecb106..3b361d2 100644 --- a/transfunctions/decorators/transfunction.py +++ b/transfunctions/decorators/transfunction.py @@ -1,12 +1,22 @@ from inspect import currentframe from types import FrameType -from typing import Union, cast +from typing import Union, cast, overload from transfunctions.transformer import FunctionTransformer from transfunctions.typing import Callable, FunctionParams, ReturnType +@overload +def transfunction(function: Callable[FunctionParams, ReturnType]) -> FunctionTransformer[FunctionParams, ReturnType]: ... + + +@overload def transfunction( + *, check_decorators: bool = True, +) -> Callable[[Callable[FunctionParams, ReturnType]], FunctionTransformer[FunctionParams, ReturnType]]: ... + + +def transfunction( # type: ignore[misc] *args: Callable[FunctionParams, ReturnType], check_decorators: bool = True, ) -> Union[Callable[[Callable[FunctionParams, ReturnType]], FunctionTransformer[FunctionParams, ReturnType]], FunctionTransformer[FunctionParams, ReturnType]]: frame = currentframe()