You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: README.md
+90-63Lines changed: 90 additions & 63 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -91,9 +91,9 @@ If any coroutine raises an error, then:
91
91
92
92
This gives every coroutine a chance to shut down gracefully. Debuggers like [`patdb`](https://github.com/patrick-kidger/patdb) offer the ability to navigate across exceptions in an exception group, allowing you to inspect the state of all coroutines that were related to the error.
93
93
94
-
### Batteries-included
94
+
### Synchronisation
95
95
96
-
We ship batteries-included with the usual collection of standard operations.
96
+
We ship batteries-included with the usual collection of standard operations for synchronisation.
You can use the following pattern to implement context managers with asynchronous creation:
189
+
190
+
```python
191
+
import contextlib
192
+
import tinyio
193
+
194
+
def my_context_manager(x):
195
+
print("Initialising...")
196
+
yield tinyio.sleep(1)
197
+
print("Initialised")
198
+
return make_context_manager(x)
199
+
200
+
@contextlib.contextmanager
201
+
def make_context_manager(x):
202
+
try:
203
+
yield x
204
+
finally:
205
+
print("Cleaning up")
206
+
207
+
def my_coro():
208
+
with (yield my_context_manager(x=5)) as val:
209
+
print(f"Got val {val}")
210
+
211
+
tinyio.Loop().run(my_coro())
212
+
```
213
+
214
+
This isn't anything fancier than just using a coroutine that returns a regular `with`-compatible context manager. See `tinyio.Semaphore` for an example of this pattern.
215
+
216
+
</details>
217
+
218
+
### Asynchronous iterators
219
+
220
+
<details><summary>Click to expand</summary>
221
+
222
+
You can use the following pattern to implement asychronous iterators:
223
+
224
+
```python
225
+
import tinyio
226
+
227
+
def slow_range(x): # this function is an iterator-of-coroutines
228
+
for i inrange(x):
229
+
yield slow_range_i(i) # this `yield` statement is seen by the `for` loop
230
+
231
+
def slow_range_i(i): # this function is a coroutine
232
+
yield tinyio.sleep(1) # this `yield` statement is seen by the `tinyio.Loop()`
233
+
return i
234
+
235
+
def my_coro():
236
+
for x in slow_range(5):
237
+
x = yield x
238
+
print(f"Got {x}")
239
+
240
+
tinyio.Loop().run(my_coro())
241
+
```
242
+
243
+
Here we just have `yield` being used in a couple of different ways that you're already used to:
244
+
-as a regular Python generator/iterator;
245
+
-as a `tinyio` coroutine.
246
+
247
+
For an example of this, see `tinyio.as_completed`.
248
+
249
+
</details>
250
+
184
251
### Integration with `asyncio` and `trio`
185
252
186
253
We have support for putting `trio` event loops within `asyncio`/`trio` event loops, or vice-versa.
You can use the following pattern to implement context managers with asynchronous creation:
283
-
284
-
```python
285
-
import contextlib
286
-
import tinyio
287
-
288
-
defmy_context_manager(x):
289
-
print("Initialising...")
290
-
yield tinyio.sleep(1)
291
-
print("Initialised")
292
-
return make_context_manager(x)
293
-
294
-
@contextlib.contextmanager
295
-
defmake_context_manager(x):
296
-
try:
297
-
yield x
298
-
finally:
299
-
print("Cleaning up")
300
-
301
-
defmy_coro():
302
-
with (yield my_context_manager(x=5)) as val:
303
-
print(f"Got val {val}")
304
-
305
-
tinyio.Loop().run(my_coro())
306
-
```
307
-
308
-
This isn't anything fancier than just using a coroutine that returns a regular `with`-compatible context manager. See `tinyio.Semaphore` for an example of this pattern.
309
-
310
-
</details>
311
-
312
-
### Asynchronous iterators
345
+
### Advanced topic: isolating coroutines without crashing the others
313
346
314
347
<details><summary>Click to expand</summary>
315
348
316
-
You can use the following pattern to implement asychronous iterators:
349
+
If you would like to run a coroutine (and transitively, all the coroutines it `yield`s) without crashing the entire event loop, then you can use `tinyio.isolate`. This will run the coroutine in a nested `tinyio.Loop` – so that it crashing will only affect everything in that nested loop – and then return the result or the error.
317
350
318
351
```python
319
-
import tinyio
320
-
321
-
defslow_range(x): # this function is an iterator-of-coroutines
322
-
for i inrange(x):
323
-
yield slow_range_i(i) # this `yield` statement is seen by the `for` loop
324
-
325
-
defslow_range_i(i): # this function is a coroutine
326
-
yield tinyio.sleep(1) # this `yield` statement is seen by the `tinyio.Loop()`
# at this point then `someone_elses_buggy_code`, `some_coroutine` and
364
+
#`another_coroutine` have been cancelled, but `main` is still running.
335
365
```
336
366
337
-
Here we just have `yield` being used in a couple of different ways that you're already used to:
338
-
- as a regular Python generator/iterator;
339
-
- as a `tinyio` coroutine.
340
-
341
-
For an example of this, see `tinyio.as_completed`.
342
-
343
367
</details>
344
368
345
369
## FAQ
@@ -359,7 +383,7 @@ You can distinguish it from a normal Python function by putting `if False: yield
359
383
</details>
360
384
361
385
<details>
362
-
<summary>vs <code>asyncio</code> or <code>trio</code>?.</summary>
386
+
<summary>vs <code>asyncio</code> or <code>trio</code>?</summary>
363
387
<br>
364
388
365
389
I wasted a *lot* of time trying to get correct error propagation with `asyncio`, trying to reason whether my tasks would be cleaned up correctly or not (edge-triggered vs level-triggered etc etc). `trio` is excellent but still has a one-loop-per-thread rule, and doesn't propagate cancellations to/from threads. These points inspired me to try writing my own.
@@ -371,8 +395,11 @@ I wasted a *lot* of time trying to get correct error propagation with `asyncio`,
371
395
- simple+robust error semantics (crash the whole loop if anything goes wrong);
372
396
- tiny, hackable, codebase.
373
397
374
-
However conversely, `tinyio` does not offer the ability to schedule work on the event loop whilst cleaning up from errors.
398
+
Conversely, at least right now we don't ship batteries-included with a few things:
399
+
400
+
- asynchronously launching subprocesses / making network requests / accessing the file system (in all cases use `run_in_thread` instead);
401
+
- scheduling work on the event loop whilst cleaning up from errors.
375
402
376
-
If none of the bullet points are must-haves for you, or if needing the event loop during cleanup is a dealbreaker, then either `trio` or `asyncio` are likely to be better choices. :)
403
+
If none of the bullet points are must-haves for you, or if any of its limitations are dealbreakers, then either `trio` or `asyncio` are likely to be better choices. :)
0 commit comments