A code transformation tool that automatically converts asynchronous Python code into synchronous equivalents.
npx @tessl/cli install tessl/pypi-unasync@0.6.00
# Unasync
1
2
A code transformation tool that automatically converts asynchronous Python code into synchronous equivalents by parsing and rewriting Python source files. Unasync enables developers to maintain a single asynchronous codebase while generating both async and sync versions of their libraries, supporting customizable transformation rules and seamless integration with setuptools build process.
3
4
## Package Information
5
6
- **Package Name**: unasync
7
- **Language**: Python
8
- **Installation**: `pip install unasync`
9
- **Dependencies**: `tokenize_rt`, `setuptools`
10
- **Python Support**: 3.8, 3.9, 3.10, 3.11, 3.12
11
- **Platforms**: Linux, macOS, Windows
12
13
## Core Imports
14
15
```python
16
import unasync
17
```
18
19
Specific imports:
20
21
```python
22
from unasync import Rule, unasync_files, cmdclass_build_py
23
```
24
25
## Basic Usage
26
27
### Setuptools Integration (Most Common)
28
29
```python
30
import setuptools
31
import unasync
32
33
setuptools.setup(
34
name="your_package",
35
# ... other setup configuration
36
cmdclass={'build_py': unasync.cmdclass_build_py()},
37
)
38
```
39
40
This automatically transforms files from `_async/` directories to `_sync/` directories during package build.
41
42
### Custom Directory Rules
43
44
```python
45
import setuptools
46
import unasync
47
48
# Custom transformation rules
49
rules = [
50
# Transform ahip -> hip instead of _async -> _sync
51
unasync.Rule("/ahip/", "/hip/"),
52
53
# More specific rule with additional token replacements
54
unasync.Rule("/ahip/tests/", "/hip/tests/",
55
additional_replacements={"ahip": "hip"}),
56
]
57
58
setuptools.setup(
59
name="your_package",
60
cmdclass={'build_py': unasync.cmdclass_build_py(rules=rules)},
61
)
62
```
63
64
### Direct File Transformation
65
66
```python
67
import unasync
68
69
# Transform specific files
70
unasync.unasync_files(
71
["/path/to/async/file1.py", "/path/to/async/file2.py"],
72
rules=[unasync.Rule("/async/", "/sync/")]
73
)
74
```
75
76
## Capabilities
77
78
### Rule Creation and Configuration
79
80
Creates transformation rules that define how async code should be converted to sync code, including directory mappings and custom token replacements.
81
82
```python { .api }
83
class Rule:
84
def __init__(self, fromdir, todir, additional_replacements=None):
85
"""
86
A single set of rules for 'unasync'ing file(s).
87
88
Parameters:
89
- fromdir: Source directory path containing async code (e.g., "/_async/")
90
- todir: Target directory path for generated sync code (e.g., "/_sync/")
91
- additional_replacements: Optional dict of custom token replacements
92
beyond the default async-to-sync mappings
93
94
The Rule handles path matching, file transformation, and token replacement
95
according to the specified directory mapping and token rules.
96
"""
97
```
98
99
### File Transformation
100
101
Transforms a list of Python files from async to sync using specified rules, handling tokenization, parsing, and code rewriting.
102
103
```python { .api }
104
def unasync_files(fpath_list, rules):
105
"""
106
Transform a list of files from async to sync using provided rules.
107
108
Parameters:
109
- fpath_list: List of file paths to transform
110
- rules: List of Rule instances defining transformation rules
111
112
Returns:
113
None
114
115
The function applies the most specific matching rule for each file,
116
performs tokenization and transformation, and writes sync versions
117
to the appropriate output directories with proper encoding preservation.
118
"""
119
```
120
121
### Setuptools Integration
122
123
Creates a custom build_py class for seamless integration with setuptools, automatically transforming async code during the package build process.
124
125
```python { .api }
126
def cmdclass_build_py(rules=(Rule("/_async/", "/_sync/"),)):
127
"""
128
Creates a 'build_py' class for use within setuptools 'cmdclass' parameter.
129
130
Parameters:
131
- rules: Tuple of Rule instances (defaults to (Rule("/_async/", "/_sync/"),))
132
133
Returns:
134
Custom build_py class for setuptools integration
135
136
The returned class extends setuptools.command.build_py to automatically
137
transform async files to sync versions during the build process.
138
Default rule transforms files from '/_async/' to '/_sync/' directories.
139
"""
140
```
141
142
## Transformation Rules
143
144
### Default Token Mappings
145
146
Unasync applies these default async-to-sync transformations:
147
148
- `__aenter__` → `__enter__`
149
- `__aexit__` → `__exit__`
150
- `__aiter__` → `__iter__`
151
- `__anext__` → `__next__`
152
- `asynccontextmanager` → `contextmanager`
153
- `AsyncIterable` → `Iterable`
154
- `AsyncIterator` → `Iterator`
155
- `AsyncGenerator` → `Generator`
156
- `StopAsyncIteration` → `StopIteration`
157
158
### Async/Await Removal
159
160
- `async` keywords are removed from function definitions and context managers
161
- `await` keywords are removed from expressions
162
- Class names prefixed with `Async` (followed by uppercase letter) become `Sync`
163
164
### Custom Token Replacements
165
166
```python
167
# Example: Replace custom async tokens
168
custom_rule = unasync.Rule(
169
"/my_async/",
170
"/my_sync/",
171
additional_replacements={
172
"AsyncClient": "SyncClient",
173
"async_method": "sync_method",
174
"ASYNC_CONSTANT": "SYNC_CONSTANT"
175
}
176
)
177
```
178
179
## File Organization Patterns
180
181
### Standard Pattern
182
```
183
src/
184
├── mypackage/
185
│ ├── __init__.py
186
│ ├── _async/ # Async source code
187
│ │ ├── __init__.py
188
│ │ ├── client.py
189
│ │ └── utils.py
190
│ └── _sync/ # Generated sync code (auto-created)
191
│ ├── __init__.py
192
│ ├── client.py
193
│ └── utils.py
194
```
195
196
### Custom Pattern
197
```
198
src/
199
├── mypackage/
200
│ ├── __init__.py
201
│ ├── ahip/ # Custom async directory
202
│ │ ├── __init__.py
203
│ │ └── client.py
204
│ └── hip/ # Custom sync directory (auto-created)
205
│ ├── __init__.py
206
│ └── client.py
207
```
208
209
## Error Handling
210
211
Unasync handles various scenarios gracefully:
212
213
- **Encoding Detection**: Automatically detects and preserves file encoding
214
- **Directory Creation**: Creates output directories as needed
215
- **Path Normalization**: Handles cross-platform path separators
216
- **Token Parsing**: Robust tokenization using `tokenize_rt`
217
- **Rule Precedence**: More specific directory patterns take precedence
218
219
## Advanced Usage
220
221
### Multiple Rule Sets
222
223
```python
224
rules = [
225
# General transformation
226
unasync.Rule("/_async/", "/_sync/"),
227
228
# Specific transformation with custom replacements
229
unasync.Rule("/async_tests/", "/sync_tests/",
230
additional_replacements={"TestAsync": "TestSync"}),
231
232
# Most specific rule (takes precedence)
233
unasync.Rule("/async_tests/integration/", "/sync_tests/integration/",
234
additional_replacements={"IntegrationAsync": "IntegrationSync"})
235
]
236
```
237
238
### Rule Matching
239
240
Rules are matched by directory path specificity:
241
- More specific paths (longer directory segments) take precedence
242
- Rule matching searches all subdirectory positions in file paths
243
- First matching rule at the highest specificity level is applied
244
245
### Integration Examples
246
247
```python
248
# Standard setup.py integration
249
from setuptools import setup, find_packages
250
import unasync
251
252
setup(
253
name="my-async-package",
254
packages=find_packages("src"),
255
package_dir={"": "src"},
256
cmdclass={'build_py': unasync.cmdclass_build_py()},
257
install_requires=['unasync'],
258
)
259
```
260
261
```python
262
# pyproject.toml with custom build backend
263
[build-system]
264
requires = ["setuptools", "unasync"]
265
build-backend = "setuptools.build_meta"
266
267
[tool.setuptools.cmdclass]
268
build_py = "unasync:cmdclass_build_py"
269
```