transfunctions 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 pomponchik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.1
2
+ Name: transfunctions
3
+ Version: 0.0.1
4
+ Summary: Say NO to Python fragmentation on sync and async
5
+ Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
+ Project-URL: Source, https://github.com/pomponchik/transfunctions
7
+ Project-URL: Tracker, https://github.com/pomponchik/transfunctions/issues
8
+ Keywords: async,sync to async,async to sync
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Operating System :: MacOS :: MacOS X
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+
28
+ # transfunctions
29
+
30
+ [![Downloads](https://static.pepy.tech/badge/transfunctions/month)](https://pepy.tech/project/transfunctions)
31
+ [![Downloads](https://static.pepy.tech/badge/transfunctions)](https://pepy.tech/project/transfunctions)
32
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/transfunctions/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/transfunctions?branch=develop)
33
+ [![Lines of code](https://sloc.xyz/github/pomponchik/transfunctions/?category=code)](https://github.com/boyter/scc/)
34
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/transfunctions?branch=main)](https://hitsofcode.com/github/pomponchik/transfunctions/view?branch=main)
35
+ [![Test-Package](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/transfunctions.svg)](https://pypi.python.org/pypi/transfunctions)
37
+ [![PyPI version](https://badge.fury.io/py/transfunctions.svg)](https://badge.fury.io/py/transfunctions)
38
+ [![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)
39
+
40
+ 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.
41
+
42
+
43
+ ## Table of contents
44
+
45
+ - [**Quick start**](#quick-start)
46
+ - [**The problem**](#the-problem)
47
+
48
+
49
+ ## Quick start
50
+
51
+ Install it:
52
+
53
+ ```bash
54
+ pip install transfunctions
55
+ ```
56
+
57
+ And use:
58
+
59
+ ```python
60
+ from asyncio import run
61
+ from transfunctions import (
62
+ transfunction,
63
+ sync_context,
64
+ async_context,
65
+ generator_context,
66
+ )
67
+
68
+ @transfunction
69
+ def template():
70
+ print('so, ', end='')
71
+ with sync_context:
72
+ print("it's just usual function!")
73
+ with async_context:
74
+ print("it's an async function!")
75
+ with generator_context:
76
+ print("it's a generator function!")
77
+ yield
78
+
79
+ function = template.get_usual_function()
80
+ function()
81
+ #> so, it's just usual function!
82
+
83
+ async_function = template.get_async_function()
84
+ run(async_function())
85
+ #> so, it's an async function!
86
+
87
+ generator_function = template.get_generator_function()
88
+ list(generator_function())
89
+ #> so, it's a generator function!
90
+ ```
91
+
92
+ As you can see, in this case, 3 different functions were created based on the template, including both common parts and unique ones for a specific type of function.
93
+
94
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
95
+
96
+
97
+ ## The problem
98
+
99
+ Since the `asyncio` module appeared in Python more than 10 years ago, many well-known libraries have received their asynchronous alternates. A lot of the code in the Python ecosystem has been [duplicated](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), and you probably know many such examples.
100
+
101
+ The reason for this problem is that the Python community has chosen a way to implement asynchrony expressed through syntax. There are new keywords in the language, such as `async` and `await`. Their use makes the code so-called "[multicolored](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)": all the functions in it can be red or blue, and depending on the color, the rules for calling them are different. You can only call blue functions from red ones, but not vice versa.
102
+
103
+ I must say that implementing asynchronous calls using a special syntax is not the only solution. There are languages like Go where runtime can independently determine "under the hood" where a function should be asynchronous and where not, and choose the correct way to call it. A programmer does not need to manually "colorize" their functions there. Personally, I think that choosing a different path is the mistake of the Python community, but that's not what we're discussing here.
@@ -0,0 +1,76 @@
1
+ # transfunctions
2
+
3
+ [![Downloads](https://static.pepy.tech/badge/transfunctions/month)](https://pepy.tech/project/transfunctions)
4
+ [![Downloads](https://static.pepy.tech/badge/transfunctions)](https://pepy.tech/project/transfunctions)
5
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/transfunctions/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/transfunctions?branch=develop)
6
+ [![Lines of code](https://sloc.xyz/github/pomponchik/transfunctions/?category=code)](https://github.com/boyter/scc/)
7
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/transfunctions?branch=main)](https://hitsofcode.com/github/pomponchik/transfunctions/view?branch=main)
8
+ [![Test-Package](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml)
9
+ [![Python versions](https://img.shields.io/pypi/pyversions/transfunctions.svg)](https://pypi.python.org/pypi/transfunctions)
10
+ [![PyPI version](https://badge.fury.io/py/transfunctions.svg)](https://badge.fury.io/py/transfunctions)
11
+ [![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)
12
+
13
+ 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.
14
+
15
+
16
+ ## Table of contents
17
+
18
+ - [**Quick start**](#quick-start)
19
+ - [**The problem**](#the-problem)
20
+
21
+
22
+ ## Quick start
23
+
24
+ Install it:
25
+
26
+ ```bash
27
+ pip install transfunctions
28
+ ```
29
+
30
+ And use:
31
+
32
+ ```python
33
+ from asyncio import run
34
+ from transfunctions import (
35
+ transfunction,
36
+ sync_context,
37
+ async_context,
38
+ generator_context,
39
+ )
40
+
41
+ @transfunction
42
+ def template():
43
+ print('so, ', end='')
44
+ with sync_context:
45
+ print("it's just usual function!")
46
+ with async_context:
47
+ print("it's an async function!")
48
+ with generator_context:
49
+ print("it's a generator function!")
50
+ yield
51
+
52
+ function = template.get_usual_function()
53
+ function()
54
+ #> so, it's just usual function!
55
+
56
+ async_function = template.get_async_function()
57
+ run(async_function())
58
+ #> so, it's an async function!
59
+
60
+ generator_function = template.get_generator_function()
61
+ list(generator_function())
62
+ #> so, it's a generator function!
63
+ ```
64
+
65
+ As you can see, in this case, 3 different functions were created based on the template, including both common parts and unique ones for a specific type of function.
66
+
67
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
68
+
69
+
70
+ ## The problem
71
+
72
+ Since the `asyncio` module appeared in Python more than 10 years ago, many well-known libraries have received their asynchronous alternates. A lot of the code in the Python ecosystem has been [duplicated](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), and you probably know many such examples.
73
+
74
+ The reason for this problem is that the Python community has chosen a way to implement asynchrony expressed through syntax. There are new keywords in the language, such as `async` and `await`. Their use makes the code so-called "[multicolored](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)": all the functions in it can be red or blue, and depending on the color, the rules for calling them are different. You can only call blue functions from red ones, but not vice versa.
75
+
76
+ I must say that implementing asynchronous calls using a special syntax is not the only solution. There are languages like Go where runtime can independently determine "under the hood" where a function should be asynchronous and where not, and choose the correct way to call it. A programmer does not need to manually "colorize" their functions there. Personally, I think that choosing a different path is the mistake of the Python community, but that's not what we're discussing here.
@@ -0,0 +1,46 @@
1
+ [build-system]
2
+ requires = ["setuptools==68.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "transfunctions"
7
+ version = "0.0.1"
8
+ authors = [
9
+ { name="Evgeniy Blinov", email="zheni-b@yandex.ru" },
10
+ ]
11
+ description = 'Say NO to Python fragmentation on sync and async'
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ classifiers = [
15
+ "Operating System :: OS Independent",
16
+ 'Operating System :: MacOS :: MacOS X',
17
+ 'Operating System :: Microsoft :: Windows',
18
+ 'Operating System :: POSIX',
19
+ 'Operating System :: POSIX :: Linux',
20
+ 'Programming Language :: Python',
21
+ 'Programming Language :: Python :: 3.8',
22
+ 'Programming Language :: Python :: 3.9',
23
+ 'Programming Language :: Python :: 3.10',
24
+ 'Programming Language :: Python :: 3.11',
25
+ 'Programming Language :: Python :: 3.12',
26
+ 'Programming Language :: Python :: 3.13',
27
+ 'License :: OSI Approved :: MIT License',
28
+ 'Intended Audience :: Developers',
29
+ 'Topic :: Software Development :: Libraries',
30
+ ]
31
+ keywords = [
32
+ 'async',
33
+ 'sync to async',
34
+ 'async to sync',
35
+ ]
36
+
37
+ [tool.setuptools.package-data]
38
+ "transfunctions" = ["py.typed"]
39
+
40
+ [tool.mutmut]
41
+ paths_to_mutate="transfunctions"
42
+ runner="pytest"
43
+
44
+ [project.urls]
45
+ 'Source' = 'https://github.com/pomponchik/transfunctions'
46
+ 'Tracker' = 'https://github.com/pomponchik/transfunctions/issues'
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,14 @@
1
+ from transfunctions.decorator import transfunction as transfunction
2
+
3
+ from transfunctions.markers import (
4
+ async_context as async_context,
5
+ sync_context as sync_context,
6
+ generator_context as generator_context,
7
+ await_it as await_it,
8
+ )
9
+
10
+ from transfunctions.errors import (
11
+ CallTransfunctionDirectlyError as CallTransfunctionDirectlyError,
12
+ DualUseOfDecoratorError as DualUseOfDecoratorError,
13
+ WrongDecoratorSyntaxError as WrongDecoratorSyntaxError,
14
+ )
@@ -0,0 +1,6 @@
1
+ from transfunctions.transformer import FunctionTransformer
2
+ from inspect import currentframe
3
+
4
+
5
+ def transfunction(function):
6
+ return FunctionTransformer(function, currentframe().f_back.f_lineno)
@@ -0,0 +1,10 @@
1
+ class CallTransfunctionDirectlyError(NotImplementedError):
2
+ pass
3
+
4
+
5
+ class DualUseOfDecoratorError(SyntaxError):
6
+ pass
7
+
8
+
9
+ class WrongDecoratorSyntaxError(SyntaxError):
10
+ pass
@@ -0,0 +1,24 @@
1
+ from typing import Any
2
+ from contextlib import contextmanager
3
+
4
+
5
+ @contextmanager
6
+ def create_async_context():
7
+ yield
8
+
9
+ @contextmanager
10
+ def create_sync_context():
11
+ yield
12
+
13
+ @contextmanager
14
+ def create_generator_context():
15
+ yield
16
+
17
+
18
+ async_context = create_async_context()
19
+ sync_context = create_sync_context()
20
+ generator_context = create_generator_context()
21
+
22
+
23
+ def await_it(some_expression: Any):
24
+ pass
File without changes
@@ -0,0 +1,196 @@
1
+ from sys import version_info
2
+ from typing import Optional, Union, List, Any
3
+ from types import FunctionType
4
+ from collections.abc import Callable
5
+ from inspect import isfunction, iscoroutinefunction, getsource, getfile
6
+ from ast import parse, NodeTransformer, Expr, AST, FunctionDef, AsyncFunctionDef, increment_lineno, Await, Return, Name, Load, Assign, Constant, Store, arguments
7
+ from functools import wraps, update_wrapper
8
+
9
+ from transfunctions.errors import CallTransfunctionDirectlyError, DualUseOfDecoratorError, WrongDecoratorSyntaxError
10
+
11
+
12
+ class FunctionTransformer:
13
+ def __init__(self, function: Callable, decorator_lineno: int) -> None:
14
+ if isinstance(function, type(self)):
15
+ raise DualUseOfDecoratorError("You cannot use the 'transfunction' decorator twice for the same function.")
16
+ if not isfunction(function):
17
+ raise ValueError("Only regular or generator functions can be used as a template for @transfunction.")
18
+ if iscoroutinefunction(function):
19
+ raise ValueError("Only regular or generator functions can be used as a template for @transfunction. You can't use async functions.")
20
+ if self.is_lambda(function):
21
+ raise ValueError("Only regular or generator functions can be used as a template for @transfunction. Don't use lambdas here.")
22
+
23
+ self.function = function
24
+ self.decorator_lineno = decorator_lineno
25
+
26
+ def __call__(self, *args: Any, **kwargs: Any) -> None:
27
+ raise CallTransfunctionDirectlyError("You can't call a transfunction object directly, create a function, a generator function or a coroutine function from it.")
28
+
29
+ @staticmethod
30
+ def is_lambda(function: Callable) -> bool:
31
+ # https://stackoverflow.com/a/3655857/14522393
32
+ lambda_example = lambda: 0 # noqa: E731
33
+ return isinstance(function, type(lambda_example)) and function.__name__ == lambda_example.__name__
34
+
35
+ def get_usual_function(self):
36
+ return self.extract_context('sync_context')
37
+
38
+ def get_async_function(self):
39
+ original_function = self.function
40
+
41
+ class ConvertSyncFunctionToAsync(NodeTransformer):
42
+ def visit_FunctionDef(self, node: Expr) -> Optional[Union[AST, List[AST]]]:
43
+ if node.name == original_function.__name__:
44
+ return AsyncFunctionDef(
45
+ name=original_function.__name__,
46
+ args=node.args,
47
+ body=node.body,
48
+ decorator_list=node.decorator_list,
49
+ lineno=node.lineno,
50
+ col_offset=node.col_offset,
51
+ )
52
+ return node
53
+
54
+ class ExtractAwaitExpressions(NodeTransformer):
55
+ def visit_Call(self, node: Expr) -> Optional[Union[AST, List[AST]]]:
56
+ if node.func.id == 'await_it':
57
+ return Await(
58
+ value=node.args[0],
59
+ lineno=node.lineno,
60
+ col_offset=node.col_offset,
61
+ )
62
+ return node
63
+
64
+ return self.extract_context(
65
+ 'async_context',
66
+ addictional_transformers=[
67
+ ConvertSyncFunctionToAsync(),
68
+ ExtractAwaitExpressions(),
69
+ ],
70
+ )
71
+
72
+ def get_generator_function(self):
73
+ return self.extract_context('generator_context')
74
+
75
+ @staticmethod
76
+ def clear_spaces_from_source_code(source_code: str) -> str:
77
+ splitted_source_code = source_code.split('\n')
78
+
79
+ indent = 0
80
+ for letter in splitted_source_code[0]:
81
+ if letter.isspace():
82
+ indent += 1
83
+ else:
84
+ break
85
+
86
+ new_splitted_source_code = [x[indent:] for x in splitted_source_code]
87
+
88
+ return '\n'.join(new_splitted_source_code)
89
+
90
+
91
+ def extract_context(self, context_name: str, addictional_transformers: Optional[List[NodeTransformer]] = None):
92
+ source_code = getsource(self.function)
93
+ converted_source_code = self.clear_spaces_from_source_code(source_code)
94
+ tree = parse(converted_source_code)
95
+ original_function = self.function
96
+ transfunction_decorator = None
97
+
98
+ class RewriteContexts(NodeTransformer):
99
+ def visit_With(self, node: Expr) -> Optional[Union[AST, List[AST]]]:
100
+ if len(node.items) == 1 and node.items[0].context_expr.id == context_name:
101
+ return node.body
102
+ elif len(node.items) == 1 and node.items[0].context_expr.id != context_name and context_name in ('async_context', 'sync_context', 'generator_context'):
103
+ return None
104
+ return node
105
+
106
+ class DeleteDecorator(NodeTransformer):
107
+ def visit_FunctionDef(self, node: Expr) -> Optional[Union[AST, List[AST]]]:
108
+ if node.name == original_function.__name__:
109
+ nonlocal transfunction_decorator
110
+ transfunction_decorator = None
111
+
112
+ if not node.decorator_list:
113
+ raise WrongDecoratorSyntaxError("The @transfunction decorator can only be used with the '@' symbol. Don't use it as a regular function. Also, don't rename it.")
114
+
115
+ for decorator in node.decorator_list:
116
+ if decorator.id != 'transfunction':
117
+ raise WrongDecoratorSyntaxError('The @transfunction decorator cannot be used in conjunction with other decorators.')
118
+ else:
119
+ if transfunction_decorator is not None:
120
+ raise DualUseOfDecoratorError("You cannot use the 'transfunction' decorator twice for the same function.")
121
+ transfunction_decorator = decorator
122
+
123
+ node.decorator_list = []
124
+ return node
125
+
126
+
127
+ RewriteContexts().visit(tree)
128
+ DeleteDecorator().visit(tree)
129
+
130
+ if addictional_transformers is not None:
131
+ for addictional_transformer in addictional_transformers:
132
+ addictional_transformer.visit(tree)
133
+
134
+ tree = self.wrap_ast_by_closures(tree)
135
+
136
+ if version_info.minor > 10:
137
+ increment_lineno(tree, n=(self.decorator_lineno - transfunction_decorator.lineno))
138
+ else:
139
+ increment_lineno(tree, n=(self.decorator_lineno - transfunction_decorator.lineno - 1))
140
+
141
+ code = compile(tree, filename=getfile(self.function), mode='exec')
142
+ namespace = {}
143
+ exec(code, namespace)
144
+ function_factory = namespace['wrapper']
145
+ result = function_factory()
146
+ result = self.rewrite_globals_and_closure(result)
147
+ result = wraps(self.function)(result)
148
+ return result
149
+
150
+ def wrap_ast_by_closures(self, tree):
151
+ old_functiondef = tree.body[0]
152
+
153
+ tree.body[0] = FunctionDef(
154
+ name='wrapper',
155
+ 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] + [
156
+ old_functiondef,
157
+ Return(value=Name(id=self.function.__name__, ctx=Load(), col_offset=0), col_offset=0),
158
+ ],
159
+ col_offset=0,
160
+ args=arguments(
161
+ posonlyargs=[],
162
+ args=[],
163
+ kwonlyargs=[],
164
+ kw_defaults=[],
165
+ defaults=[],
166
+ ),
167
+ decorator_list=[],
168
+ )
169
+
170
+ return tree
171
+
172
+
173
+ def rewrite_globals_and_closure(self, function):
174
+ # https://stackoverflow.com/a/13503277/14522393
175
+ all_new_closure_names = set(self.function.__code__.co_freevars)
176
+
177
+ if self.function.__closure__ is not None:
178
+ old_function_closure_variables = {name: cell for name, cell in zip(self.function.__code__.co_freevars, self.function.__closure__)}
179
+ filtered_closure = tuple([cell for name, cell in old_function_closure_variables.items() if name in all_new_closure_names])
180
+ names = tuple([name for name, cell in old_function_closure_variables.items() if name in all_new_closure_names])
181
+ new_code = function.__code__.replace(co_freevars=names)
182
+ else:
183
+ filtered_closure = None
184
+ new_code = function.__code__
185
+
186
+ new_function = FunctionType(
187
+ new_code,
188
+ self.function.__globals__,
189
+ name=self.function.__name__,
190
+ argdefs=self.function.__defaults__,
191
+ closure=filtered_closure,
192
+ )
193
+
194
+ new_function = update_wrapper(new_function, function)
195
+ new_function.__kwdefaults__ = function.__kwdefaults__
196
+ return new_function
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.1
2
+ Name: transfunctions
3
+ Version: 0.0.1
4
+ Summary: Say NO to Python fragmentation on sync and async
5
+ Author-email: Evgeniy Blinov <zheni-b@yandex.ru>
6
+ Project-URL: Source, https://github.com/pomponchik/transfunctions
7
+ Project-URL: Tracker, https://github.com/pomponchik/transfunctions/issues
8
+ Keywords: async,sync to async,async to sync
9
+ Classifier: Operating System :: OS Independent
10
+ Classifier: Operating System :: MacOS :: MacOS X
11
+ Classifier: Operating System :: Microsoft :: Windows
12
+ Classifier: Operating System :: POSIX
13
+ Classifier: Operating System :: POSIX :: Linux
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: License :: OSI Approved :: MIT License
22
+ Classifier: Intended Audience :: Developers
23
+ Classifier: Topic :: Software Development :: Libraries
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+
28
+ # transfunctions
29
+
30
+ [![Downloads](https://static.pepy.tech/badge/transfunctions/month)](https://pepy.tech/project/transfunctions)
31
+ [![Downloads](https://static.pepy.tech/badge/transfunctions)](https://pepy.tech/project/transfunctions)
32
+ [![Coverage Status](https://coveralls.io/repos/github/pomponchik/transfunctions/badge.svg?branch=main)](https://coveralls.io/github/pomponchik/transfunctions?branch=develop)
33
+ [![Lines of code](https://sloc.xyz/github/pomponchik/transfunctions/?category=code)](https://github.com/boyter/scc/)
34
+ [![Hits-of-Code](https://hitsofcode.com/github/pomponchik/transfunctions?branch=main)](https://hitsofcode.com/github/pomponchik/transfunctions/view?branch=main)
35
+ [![Test-Package](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml/badge.svg)](https://github.com/pomponchik/transfunctions/actions/workflows/tests_and_coverage.yml)
36
+ [![Python versions](https://img.shields.io/pypi/pyversions/transfunctions.svg)](https://pypi.python.org/pypi/transfunctions)
37
+ [![PyPI version](https://badge.fury.io/py/transfunctions.svg)](https://badge.fury.io/py/transfunctions)
38
+ [![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)
39
+
40
+ 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.
41
+
42
+
43
+ ## Table of contents
44
+
45
+ - [**Quick start**](#quick-start)
46
+ - [**The problem**](#the-problem)
47
+
48
+
49
+ ## Quick start
50
+
51
+ Install it:
52
+
53
+ ```bash
54
+ pip install transfunctions
55
+ ```
56
+
57
+ And use:
58
+
59
+ ```python
60
+ from asyncio import run
61
+ from transfunctions import (
62
+ transfunction,
63
+ sync_context,
64
+ async_context,
65
+ generator_context,
66
+ )
67
+
68
+ @transfunction
69
+ def template():
70
+ print('so, ', end='')
71
+ with sync_context:
72
+ print("it's just usual function!")
73
+ with async_context:
74
+ print("it's an async function!")
75
+ with generator_context:
76
+ print("it's a generator function!")
77
+ yield
78
+
79
+ function = template.get_usual_function()
80
+ function()
81
+ #> so, it's just usual function!
82
+
83
+ async_function = template.get_async_function()
84
+ run(async_function())
85
+ #> so, it's an async function!
86
+
87
+ generator_function = template.get_generator_function()
88
+ list(generator_function())
89
+ #> so, it's a generator function!
90
+ ```
91
+
92
+ As you can see, in this case, 3 different functions were created based on the template, including both common parts and unique ones for a specific type of function.
93
+
94
+ You can also quickly try out this and other packages without having to install using [instld](https://github.com/pomponchik/instld).
95
+
96
+
97
+ ## The problem
98
+
99
+ Since the `asyncio` module appeared in Python more than 10 years ago, many well-known libraries have received their asynchronous alternates. A lot of the code in the Python ecosystem has been [duplicated](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself), and you probably know many such examples.
100
+
101
+ The reason for this problem is that the Python community has chosen a way to implement asynchrony expressed through syntax. There are new keywords in the language, such as `async` and `await`. Their use makes the code so-called "[multicolored](https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/)": all the functions in it can be red or blue, and depending on the color, the rules for calling them are different. You can only call blue functions from red ones, but not vice versa.
102
+
103
+ I must say that implementing asynchronous calls using a special syntax is not the only solution. There are languages like Go where runtime can independently determine "under the hood" where a function should be asynchronous and where not, and choose the correct way to call it. A programmer does not need to manually "colorize" their functions there. Personally, I think that choosing a different path is the mistake of the Python community, but that's not what we're discussing here.
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ transfunctions/__init__.py
5
+ transfunctions/decorator.py
6
+ transfunctions/errors.py
7
+ transfunctions/markers.py
8
+ transfunctions/py.typed
9
+ transfunctions/transformer.py
10
+ transfunctions.egg-info/PKG-INFO
11
+ transfunctions.egg-info/SOURCES.txt
12
+ transfunctions.egg-info/dependency_links.txt
13
+ transfunctions.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ transfunctions