From c86c883aa4b1095340a908d61b82d76a8298beeb Mon Sep 17 00:00:00 2001 From: esblinov Date: Tue, 22 Jul 2025 23:16:20 +0300 Subject: [PATCH 1/9] logo --- docs/assets/logo_3.png | Bin 0 -> 17539 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/assets/logo_3.png diff --git a/docs/assets/logo_3.png b/docs/assets/logo_3.png new file mode 100644 index 0000000000000000000000000000000000000000..5461a86877144c58bd64c4fbbd1bc3836c1d7b47 GIT binary patch literal 17539 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 literal 0 HcmV?d00001 From 2e9f592b464fe5374de40ad4a0a2cf28ee07d808 Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 23 Jul 2025 21:47:26 +0300 Subject: [PATCH 2/9] automatically add pass to empty functions --- transfunctions/transformer.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index e5f17ba..ad784a6 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -3,7 +3,7 @@ from types import MethodType, FunctionType from collections.abc import Callable from inspect import isfunction, iscoroutinefunction, getsource, getfile -from ast import parse, NodeTransformer, AST, FunctionDef, AsyncFunctionDef, increment_lineno, Await, Call, With, Return, Name, Load, Assign, Constant, Store, arguments +from ast import parse, NodeTransformer, AST, FunctionDef, AsyncFunctionDef, increment_lineno, Await, Call, With, Return, Name, Load, Assign, Constant, Store, Pass, arguments from functools import wraps, update_wrapper from dill.source import getsource as dill_getsource # type: ignore[import-untyped] @@ -144,6 +144,13 @@ def visit_FunctionDef(self, node: FunctionDef) -> Optional[Union[AST, List[AST]] RewriteContexts().visit(tree) DeleteDecorator().visit(tree) + if not tree.body[0].body: + tree.body[0].body.append( + Pass( + col_offset=tree.body[0].col_offset, + ), + ) + if addictional_transformers is not None: for addictional_transformer in addictional_transformers: addictional_transformer.visit(tree) From 6e7ae7ea54bba970586a5b78d6b758933d74680f Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 23 Jul 2025 23:38:57 +0300 Subject: [PATCH 3/9] new tests --- tests/units/decorators/test_superfunction.py | 4 -- tests/units/decorators/test_transfunction.py | 44 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index 34dcf4b..001da65 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -382,7 +382,6 @@ def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_empty_retu def function(): with async_context: return - pass def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_return_true_in_async_block(): @@ -390,7 +389,6 @@ def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_return_tru def function(): with async_context: return True - pass def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_empty_return_in_generator_block(): @@ -398,7 +396,6 @@ def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_empty_retu def function(): with generator_context: return - pass def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_return_true_in_generator_block(): @@ -406,4 +403,3 @@ def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_return_tru def function(): with generator_context: return True - pass diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 1b8e181..e9821b1 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -771,3 +771,47 @@ def template(): with pytest.raises(WrongDecoratorSyntaxError, match=full_match('The @transfunction decorator cannot be used in conjunction with other decorators.')): template.get_usual_function() + + +def test_create_empty_usual_function_without_arguments(): + @transfunction + def template(): + with async_context: + pass + + function = template.get_usual_function() + + assert function() is None + + +def test_create_empty_usual_function_with_arguments(): + @transfunction + def template(a, b): + with async_context: + return a + b + + function = template.get_usual_function() + + assert function(1, 2) is None + + +def test_create_empty_async_function_without_arguments(): + @transfunction + def template(): + with sync_context: + pass + + function = template.get_async_function() + + assert run(function()) is None + + +def test_create_empty_async_function_with_arguments(): + @transfunction + def template(a, b): + with sync_context: + return a + b + + function = template.get_async_function() + + assert run(function(1, 2)) is None From 3cbec9cce37af4db48f272a97cbac017e198f64e Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 23 Jul 2025 23:39:28 +0300 Subject: [PATCH 4/9] new version tag --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 0662d55..10afcb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "transfunctions" -version = "0.0.6" +version = "0.0.7" authors = [ { name="Evgeniy Blinov", email="zheni-b@yandex.ru" }, ] From 04b1848dfeb7567581dd6f14583777c945111b7b Mon Sep 17 00:00:00 2001 From: esblinov Date: Wed, 23 Jul 2025 23:42:36 +0300 Subject: [PATCH 5/9] test cases --- tests/units/decorators/test_superfunction.py | 4 ++-- tests/units/decorators/test_transfunction.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index 001da65..0a034c0 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -11,15 +11,15 @@ """ Что нужно проверить: -2. При попытке вызвать без тильды суперфункцию, в которой есть return или raise, должно подниматься исключение, причем . 3. Трейсбек исключения из п. 2 информативен (т.е. содержит конкретную строчку кода, и короткий). Но есть возможность увидеть полный "настоящий" трейсбек. 4. Базовые кейсы работают в глобальном скоупе. -6. С синтаксисом ~ нормально поднимаются исключения. Что проверено: 1. Все базово работает без аргументов и с аргументами, для обычных, асинк и генераторных функций. 5. С использованием синтаксиса ~ для вызова обычных функций можно возвращать значения, с аргументами и без. +2. При попытке вызвать без тильды суперфункцию, в которой есть return или raise, должно подниматься исключение. +6. С синтаксисом ~ нормально поднимаются исключения. """ diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index e9821b1..006150f 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -26,7 +26,6 @@ 20. При попытке сгенерировать асинк функцию, в которой есть "yield" или "yield from" - поднимется исключение. 22. При подмене имен переменных из списка все продолжает работать: 'transfunction', 'create_async_context', 'create_sync_context', 'create_generator_context', 'await_it'. 25. Кэширование работает. -26. Если функция-шаблон содержит исключительно sync_context блок, при генерации async функции в ее тело будет подставлено pass, и по аналогии с другими типами. 2 фаза: @@ -50,6 +49,7 @@ 11. Работает директива global. 24. Модуль у порождаемых функций такой же, как у шаблона-оригинала. 12. Декоратор @transfunction можно использовать на методах (в т.ч. асинк и генераторных). +26. Если функция-шаблон содержит исключительно sync_context блок, при генерации async функции в ее тело будет подставлено pass, и по аналогии с другими типами. Исключение - генераторы, там потом будет проверка на наличие yield. """ @transfunction From 5316f90d6b5559bd3973923bf1c236ce97cf3717 Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 24 Jul 2025 00:02:14 +0300 Subject: [PATCH 6/9] more tests --- tests/units/decorators/test_superfunction.py | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/units/decorators/test_superfunction.py b/tests/units/decorators/test_superfunction.py index 0a034c0..ead1d14 100644 --- a/tests/units/decorators/test_superfunction.py +++ b/tests/units/decorators/test_superfunction.py @@ -403,3 +403,39 @@ def test_there_are_no_exceptions_if_not_tilde_mode_and_in_function_is_return_tru def function(): with generator_context: return True + + +def test_async_function_with_all_content_in_generator_context(): + @superfunction + def function(): + with generator_context: + return True + + assert run(function()) is None + + +def test_async_function_with_all_content_in_sync_context(): + @superfunction + def function(): + with sync_context: + return True + + assert run(function()) is None + + +def test_usual_tilde_function_with_all_content_in_generator_context(): + @superfunction + def function(): + with generator_context: + return True + + assert ~function() is None + + +def test_usual_tilde_function_with_all_content_in_async_context(): + @superfunction + def function(): + with async_context: + return True + + assert ~function() is None From af88a57f7542fc6681c229097117dbafb0fa2e9f Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 24 Jul 2025 00:38:30 +0300 Subject: [PATCH 7/9] more tests --- tests/units/decorators/test_transfunction.py | 377 ++++++++++++++++++- transfunctions/transformer.py | 14 +- 2 files changed, 386 insertions(+), 5 deletions(-) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 006150f..041b277 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -1,6 +1,7 @@ import traceback from inspect import isfunction, iscoroutinefunction, isgeneratorfunction, getsourcelines from asyncio import run +from contextlib import contextmanager import pytest import full_match @@ -19,13 +20,15 @@ 23. Если использовать 'await_it' вне асинк блока, поднимется исключение. 14. Все работает с любыми уровнями вложенности (попробовать объявить функцию внутри функции). -15. Сторонние контекстные менеджеры работают, как со скобками, так и без. +15. Сторонние контекстные менеджеры работают, как со скобками, так и без, как вне контекстных маркеров, так и внутри. 16. При попытке использовать маркерные контекстные менеджеры со скобками поднимается информативное исключение. 18. При попытке сгенерировать генераторную функцию без "yield" или "yield from" - поднимается исключение. 19. При попытке сгенерировать обычную функцию, в которой есть "yield" или "yield from" - поднимется исключение. 20. При попытке сгенерировать асинк функцию, в которой есть "yield" или "yield from" - поднимется исключение. 22. При подмене имен переменных из списка все продолжает работать: 'transfunction', 'create_async_context', 'create_sync_context', 'create_generator_context', 'await_it'. 25. Кэширование работает. +27. Работает совмещение 2 сторонних контекстных менеджеров (как со скобками, так и без). +28. Нельзя использовать контекстные маркеры вместе со сторонними контекстными менеджерами. 2 фаза: @@ -815,3 +818,375 @@ def template(a, b): function = template.get_async_function() assert run(function(1, 2)) is None + + +def test_other_context_managers_with_empty_parentness_are_working_in_usual_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with context_manager_with_parentnes() as something: + return something + + function = template.get_usual_function() + + assert function() == 123 + + +def test_other_context_managers_with_empty_parentness_are_working_in_usual_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with context_manager_with_parentnes() as something: + return something + a + b + + function = template.get_usual_function() + + assert function(1, 2) == 126 + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_usual_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with context_manager_with_parentnes(4) as something: + return something + + function = template.get_usual_function() + + assert function() == 127 + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_usual_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with context_manager_with_parentnes(4) as something: + return something + a + b + + function = template.get_usual_function() + + assert function(1, 2) == 130 + + +def test_other_context_managers_with_empty_parentness_are_working_in_async_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with context_manager_with_parentnes() as something: + return something + + function = template.get_async_function() + + assert run(function()) == 123 + + +def test_other_context_managers_with_empty_parentness_are_working_in_async_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with context_manager_with_parentnes() as something: + return something + a + b + + function = template.get_async_function() + + assert run(function(1, 2)) == 126 + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_async_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with context_manager_with_parentnes(4) as something: + return something + + function = template.get_async_function() + + assert run(function()) == 127 + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_async_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with context_manager_with_parentnes(4) as something: + return something + a + b + + function = template.get_async_function() + + assert run(function(1, 2)) == 130 + + +def test_other_context_managers_with_empty_parentness_are_working_in_generator_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with context_manager_with_parentnes() as something: + yield something + + function = template.get_generator_function() + + assert list(function()) == [123] + + +def test_other_context_managers_with_empty_parentness_are_working_in_generator_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with context_manager_with_parentnes() as something: + yield something + a + b + + function = template.get_generator_function() + + assert list(function(1, 2)) == [126] + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_generator_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with context_manager_with_parentnes(4) as something: + yield something + + function = template.get_generator_function() + + assert list(function()) == [127] + + +def test_other_context_managers_with_not_empty_parentness_are_working_in_generator_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with context_manager_with_parentnes(4) as something: + yield something + a + b + + function = template.get_generator_function() + + assert list(function(1, 2)) == [130] + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_usual_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with sync_context: + with context_manager_with_parentnes() as something: + return something + + function = template.get_usual_function() + + assert function() == 123 + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_usual_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with sync_context: + with context_manager_with_parentnes() as something: + return something + a + b + + function = template.get_usual_function() + + assert function(1, 2) == 126 + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_usual_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with sync_context: + with context_manager_with_parentnes(4) as something: + return something + + function = template.get_usual_function() + + assert function() == 127 + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_usual_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with sync_context: + with context_manager_with_parentnes(4) as something: + return something + a + b + + function = template.get_usual_function() + + assert function(1, 2) == 130 + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_async_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with async_context: + with context_manager_with_parentnes() as something: + return something + + function = template.get_async_function() + + assert run(function()) == 123 + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_async_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with async_context: + with context_manager_with_parentnes() as something: + return something + a + b + + function = template.get_async_function() + + assert run(function(1, 2)) == 126 + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_async_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with async_context: + with context_manager_with_parentnes(4) as something: + return something + + function = template.get_async_function() + + assert run(function()) == 127 + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_async_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with async_context: + with context_manager_with_parentnes(4) as something: + return something + a + b + + function = template.get_async_function() + + assert run(function(1, 2)) == 130 + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_generator_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(): + with generator_context: + with context_manager_with_parentnes() as something: + yield something + + function = template.get_generator_function() + + assert list(function()) == [123] + + +def test_other_context_managers_into_context_marker_with_empty_parentness_are_working_in_generator_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(): + yield 123 + + @transfunction + def template(a, b): + with generator_context: + with context_manager_with_parentnes() as something: + yield something + a + b + + function = template.get_generator_function() + + assert list(function(1, 2)) == [126] + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_generator_function_without_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(): + with generator_context: + with context_manager_with_parentnes(4) as something: + yield something + + function = template.get_generator_function() + + assert list(function()) == [127] + + +def test_other_context_managers_into_context_marker_with_not_empty_parentness_are_working_in_generator_function_with_arguments(): + @contextmanager + def context_manager_with_parentnes(c): + yield 123 + c + + @transfunction + def template(a, b): + with generator_context: + with context_manager_with_parentnes(4) as something: + yield something + a + b + + function = template.get_generator_function() + + assert list(function(1, 2)) == [130] diff --git a/transfunctions/transformer.py b/transfunctions/transformer.py index ad784a6..8396f76 100644 --- a/transfunctions/transformer.py +++ b/transfunctions/transformer.py @@ -113,10 +113,16 @@ def extract_context(self, context_name: str, addictional_transformers: Optional[ class RewriteContexts(NodeTransformer): def visit_With(self, node: With) -> Optional[Union[AST, List[AST]]]: - if len(node.items) == 1 and node.items[0].context_expr.id == context_name: - return node.body - elif len(node.items) == 1 and node.items[0].context_expr.id != context_name and context_name in ('async_context', 'sync_context', 'generator_context'): - return None + if len(node.items) == 1: + if isinstance(node.items[0].context_expr, Name): + context_expr = node.items[0].context_expr + elif isinstance(node.items[0].context_expr, Call): + context_expr = node.items[0].context_expr.func + + if context_expr.id == context_name: + return node.body + if context_expr.id != context_name and context_expr.id in ('async_context', 'sync_context', 'generator_context'): + return None return node class DeleteDecorator(NodeTransformer): From 491fe6e260710499ebc6853407cc37e3e645199e Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 24 Jul 2025 00:39:13 +0300 Subject: [PATCH 8/9] test cases --- tests/units/decorators/test_transfunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 041b277..293b152 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -20,7 +20,6 @@ 23. Если использовать 'await_it' вне асинк блока, поднимется исключение. 14. Все работает с любыми уровнями вложенности (попробовать объявить функцию внутри функции). -15. Сторонние контекстные менеджеры работают, как со скобками, так и без, как вне контекстных маркеров, так и внутри. 16. При попытке использовать маркерные контекстные менеджеры со скобками поднимается информативное исключение. 18. При попытке сгенерировать генераторную функцию без "yield" или "yield from" - поднимается исключение. 19. При попытке сгенерировать обычную функцию, в которой есть "yield" или "yield from" - поднимется исключение. @@ -53,6 +52,7 @@ 24. Модуль у порождаемых функций такой же, как у шаблона-оригинала. 12. Декоратор @transfunction можно использовать на методах (в т.ч. асинк и генераторных). 26. Если функция-шаблон содержит исключительно sync_context блок, при генерации async функции в ее тело будет подставлено pass, и по аналогии с другими типами. Исключение - генераторы, там потом будет проверка на наличие yield. +15. Сторонние контекстные менеджеры работают, как со скобками, так и без, как вне контекстных маркеров, так и внутри. """ @transfunction From 1aebb4b0691676bdcc26503bb9545e0b07cbbfad Mon Sep 17 00:00:00 2001 From: esblinov Date: Thu, 24 Jul 2025 00:41:41 +0300 Subject: [PATCH 9/9] test cases --- tests/units/decorators/test_transfunction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/units/decorators/test_transfunction.py b/tests/units/decorators/test_transfunction.py index 293b152..71b8a33 100644 --- a/tests/units/decorators/test_transfunction.py +++ b/tests/units/decorators/test_transfunction.py @@ -19,7 +19,7 @@ 1 фаза: 23. Если использовать 'await_it' вне асинк блока, поднимется исключение. -14. Все работает с любыми уровнями вложенности (попробовать объявить функцию внутри функции). +14. Вложенные функции запрещены, как обычные, так и асинк/генераторные. При попытке объявить - поднимется исключение. 16. При попытке использовать маркерные контекстные менеджеры со скобками поднимается информативное исключение. 18. При попытке сгенерировать генераторную функцию без "yield" или "yield from" - поднимается исключение. 19. При попытке сгенерировать обычную функцию, в которой есть "yield" или "yield from" - поднимется исключение.