Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asyncio Tutorial #79012

Open
cjrh mannequin opened this issue Sep 28, 2018 · 26 comments
Open

Asyncio Tutorial #79012

cjrh mannequin opened this issue Sep 28, 2018 · 26 comments
Labels
3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes docs Documentation in the Doc dir topic-asyncio type-feature A feature request or enhancement

Comments

@cjrh
Copy link
Mannequin

cjrh mannequin commented Sep 28, 2018

BPO 34831
Nosy @asvetlov, @cjrh, @elprans, @1st1, @willingc
PRs
  • bpo-34831: Asyncio tutorial #9748
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = None
    closed_at = None
    created_at = <Date 2018-09-28.11:36:17.041>
    labels = ['3.8', 'type-feature', '3.7', 'docs', 'expert-asyncio']
    title = 'Asyncio Tutorial'
    updated_at = <Date 2019-06-19.12:39:06.198>
    user = 'https://github.com/cjrh'

    bugs.python.org fields:

    activity = <Date 2019-06-19.12:39:06.198>
    actor = 'cjrh'
    assignee = 'docs@python'
    closed = False
    closed_date = None
    closer = None
    components = ['Documentation', 'asyncio']
    creation = <Date 2018-09-28.11:36:17.041>
    creator = 'cjrh'
    dependencies = []
    files = []
    hgrepos = []
    issue_num = 34831
    keywords = ['patch']
    message_count = 14.0
    messages = ['326628', '326665', '326789', '326977', '327266', '327271', '327275', '327284', '327286', '328201', '333088', '345661', '345755', '346039']
    nosy_count = 6.0
    nosy_names = ['asvetlov', 'cjrh', 'Elvis.Pranskevichus', 'docs@python', 'yselivanov', 'willingc']
    pr_nums = ['9748']
    priority = 'normal'
    resolution = None
    stage = 'patch review'
    status = 'open'
    superseder = None
    type = 'enhancement'
    url = 'https://bugs.python.org/issue34831'
    versions = ['Python 3.7', 'Python 3.8']

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Sep 28, 2018

    Hi Yury,

    As discussed, below is a very rough outline of a proposed TOC for an asyncio tutorial. No content has been written yet (only what you see below). I think we should nail down the TOC first.

    Asyncio Tutorial
    ================

    Proposed Table of Contents:

    • Why asyncio?

      • Reason Support "bpo-" in Misc/NEWS #1: thread safety by not using threads at all.
      • Reason Rename README to README.rst and enhance formatting #2: very many concurrent socket connections, which threads
        make cumbersome.
      • Demo: concurrency without threads
        • (only goals here are to *briefly* introduce the syntax, and then
          demonstrate concurrency without threads)
        • show two tasks, one prints out integers 0-9, while the other
          prints out letters of the alphabet, A-I.
        • pose the question: "they're running concurrently but we're
          not using threads: how is that possible?"
        • explain that there is a thing called an "event loop" behind
          the scenes that allows execution to switch between the two
          every time one of them "waits".
        • emphasize that you can easily spot where the switch can happen,
          it is where you see the "await" keyword. Switches will occur
          nowhere else.
    • The difference between functions and async def functions

      • async & await
        • show async def functions, compare to def functions.
        • use inspect module to show what things actually are, e.g. function,
          coroutine function, coroutine, generator function, generator,
          asynchronous generator function, asynchronous generator.
        • point out you can only use "await" inside an async def fn
        • point out that await <x> is an expression, and you can
          use it in most places any other expression can be used.
    • How to run async def functions

      • point out there are two different issues: (a) async def functions
        can call other functions, and other async def functions using
        await, but also, (b) how to "get started" with the first
        async def function? Answer: run the event loop.
      • show asyncio.run(), in particular running an async def main()
        function, which itself can call others.
    • Dealing with concurrent functions

    • Case Study: chat server/client (my proposal)

      • (goal is to walk through building a realistic example of using
        asyncio)
      • (This will be a lot more fun than a web-crawler. Web-crawlers are
        super boring!)
      • (I'm pretty confident the size of the code can be kept small. A
        lot can be done in under 50 lines, as I showed in the case studies
        in my book)
      • server uses streams API
      • server receives many long-lived connections
      • user can create/join a "room", and then start typing messages.
        Other connected clients in the same room will see the messages.
      • client implementation has some options:
        • could use Tkinter gui, using streams API in an event loop
          on a separate thread. (This would show how asyncio isn't some
          alien thing, but is part of python. Would also show how to
          set up asyncio to work in a separate thread. Finally, would not
          require any external dependencies, only the stdlib is needed
          for the entire case study.)
        • could use a browser client, connecting back to the server
          over a websocket connection. (This might seem simpler, but
          in fact introduces a lot more complexity than just using
          tkinter. We need to bring in html, css, probably some js and
          probably also a third-party websockets python library. I feel
          like it isn't a good fit for a stdlib python tutorial, but it
          is a more modern approach.)
        • there are not a lot of other options. terminal-based client
          might be possible, but probably hard, not cross-platform, and
          will be unappealing to many people.
      • When describing the code, point out:
        • you have to choose a "message protocol" (size-prefixed is fine)
        • you must put send and recv in separate tasks
        • server will "keep track" of connected clients and the room
          (or rooms?) they've joined
        • startup and shutdown, specific references to the new run()
          function
        • ?

    @cjrh cjrh mannequin added 3.7 (EOL) end of life 3.8 (EOL) end of life labels Sep 28, 2018
    @cjrh cjrh mannequin assigned docspython Sep 28, 2018
    @cjrh cjrh mannequin added docs Documentation in the Doc dir topic-asyncio type-feature A feature request or enhancement labels Sep 28, 2018
    @1st1
    Copy link
    Member

    1st1 commented Sep 29, 2018

    I like this, great job!

    Couple of thoughts on how we should organize this:

    • I think we should stick to your structure and push things to docs.python.org as soon as every next section is somewhat ready.

    • Every big section should probably have its own page, linking prev/next tutorial pages.

    • I'd organize the tutorial in a dedicated directory like "Doc/library/asyncio-tutorial/". Context: I wanted to reorganize all current "Doc/library/asyncio*.rst" files under one dir too, but decided to keep the existing file structure to avoid breaking links to the docs from SO/google). We shouldn't repeat that mistake again.

    BTW, maybe we should consider using the new iPythonn async repl: https://blog.jupyter.org/ipython-7-0-async-repl-a35ce050f7f7 What do you think about that?

    Some comments on the proposed TOC:

    This section is super important to get right, as I see people ask the "why and where should I use asyncio" _all_ _the_ _time_. So I'll expand on this section in a detail.

    Reason #1 and #2 are correct, but I think we should really focus on making #1 comprehensive and clear:

    • async/await makes all context switches visible; that makes it easy to spot race conditions and reason about your code (think about ORMs that can make 100s of queries to render one web page); you've likely seen https://glyph.twistedmatrix.com/2014/02/unyielding.html

    • in general, all datastructures are safe for async (we cannot say same for threads);

    • an async/await library means that it's safe to use it in concurrent async/await code (you can never be sure if some library is thread-safe, even if it claims that);

    • language constructs like 'async for' and 'async with' enable structured concurrency;

    And on #2:

    • high-throughput IO or 1000s of long-living connections are only doable with asyncio

    • if you don't need to scale your code right now but might need in near future investing in async/await is wise

    • How to run async def functions
      • point out there are two different issues: (a) async def functions
        can call other functions, and other async def functions using
        await, but also, (b) how to "get started" with the first
        async def function? Answer: run the event loop.

    Just a quick note: I'd try to not mention the low-level loop APIs as long as possible (e.g. no loop.run_until_complete() etc).

    Right, we'll need to update the asyncio-task.rst file. I think we'll collapse first two section into one ("Coroutines" and "Awaitables" into "Awaitables") and link the tutorial from that new section.

    • Case Study: chat server/client (my proposal)
      [..]
      • server uses streams API

    Yay for streams!

    [..]
    - client implementation has some options:

    I never use tkinter myself :( I remember trying to use it and it didn't work on my macOS. So I'd try to either:

    • build a simple browser app (that would require us to implement HTTP 0.9 which can be fun);
    • build a terminal app;
    • use iPython repl to connect to our asyncio server (might end up being more complicated than the first two options).

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Oct 1, 2018

    • I think we should stick to your structure and push things to docs.python.org as soon as every next section is somewhat ready.

    Ok. I'll get a PR going for the start page of the tutorial.

    • Every big section should probably have its own page, linking prev/next tutorial pages.
    • I'd organize the tutorial in a dedicated directory like "Doc/library/asyncio-tutorial/".

    Agree.

    BTW, maybe we should consider using the new iPythonn async repl: https://blog.jupyter.org/ipython-7-0-async-repl-a35ce050f7f7 What do you think about that?

    I saw ​Matthias' tweets about that recently too. It's cool! but...for teaching purposes it's not great to introduce a whole new complex tool (ipython) to explain a different complex tool (asyncio). My experience is that *every* single new thing that is mentioned adds cognitive load for learners. For this tutorial my feeling is to keep as much to "ordinary" Python stuff as possible, i.e., stdlib.

    Just a quick note: I'd try to not mention the low-level loop APIs as long as possible (e.g. no loop.run_until_complete() etc).

    For sure, I agree with you 100% on this. But I find it hard to do as soon as I have to make a real thing. I think you're right that we focus initially on only high-level stuff first (and for most of the tut). That is doable.

    I think we'll collapse first two section into one ("Coroutines" and "Awaitables" into "Awaitables") and link the tutorial from that new section.

    ok

    Yay for streams!
    I never use tkinter myself :( I remember trying to use it and it didn't work on my macOS. So I'd try to either:

    • build a simple browser app (that would require us to implement HTTP 0.9 which can be fun);
    • build a terminal app;
    • use iPython repl to connect to our asyncio server (might end up being more complicated than the first two options).

    I too have bashed my head for many hours over the years trying to get Tkinter to work on Mac, but a lot of work has gone into this recently and the newer (release) Python's have bundled Tk 8.6: https://www.python.org/download/mac/tcltk/ (this is what learners will prob use on Mac)

    Tkinter gets a bad rap, but it's really quite powerful--and portable. Runs great on a Raspberry Pi for example.

    Noticing your hesitation towards tkinter ;) , I spent a few hours on Sunday sketching out my "chat server/client" idea a little more, using Tkinter for the client UI:

    https://github.com/cjrh/chat

    (Notice especially in the README all the different aspects of asyncio, streams etc. that we would be able to cover and explain with an actual use-case. THESE are the kinds of tricky things people desperately want help with.)

    It's still rough obviously (I can prob reduce the total LOC footprint by 20% & I'm sure you can improve on some parts) but I just wanted to show you something runnable you can prod and poke to give a concrete idea of what I'm suggesting. It works on Windows, should work on Linux but I haven't tested yet.

    My proposal is that we slowly build up towards this, starting with the "hello world" simple case (asyncio.run calling main() which prints out "hello world" or something), and then adding the necessary features, piece by piece, with commentary along the way on what each piece does, and why it is done in a particular way. (I specifically like to write like this: simplistic case first, and then improve incrementally)

    • Only requires stdlib (so we don't have to explain or link to pip/virtualenv etc. etc.)
    • shows a wide range of *interoperating* asyncio features in a condensed app
    • client has a proper GUI, i.e. "looks" like an actual application, not just an ugly CLI thing
    • client handles reconnection, if the server goes down and comes back later.
    • using signal handling to trigger shutdown (esp. the server)
    • signal handling works on Windows (CTRL-C and CTRL-BREAK near-instant controlled shutdown)
    • server is 100% asyncio (so that situation is covered), but client requires marrying two loops (so this situation is also covered), one for tkinter and one for asyncio. (This is a common problem, not just with UI frameworks but also with game programming frameworks like pygame, pyarcade and so on. Again, this is the kind of problem many people ask for help with.)
    • thus, an example showing how to run asyncio in a thread. (asyncio.run works great in a thread, nice job!)
    • an actual SSL example that works (this was surprisingly hard to find, eventually found one at PyMOTW)

    I fully realise that this case study implementation might look weird and ugly, and we don't really want to mention threads at all, and we don't want to explicitly refer to the loop, or create a Future instance, etc., but this is the kind of case study that will give people guidance on how to handle these *actual problems* that they are going to come across.

    If you have a look and still don't want to go this way, that's ok, I'm happy to go with a different suggestion.

    @1st1
    Copy link
    Member

    1st1 commented Oct 3, 2018

    I too have bashed my head for many hours over the years trying to get Tkinter to work on Mac, but a lot of work has gone into this recently and the newer (release) Python's have bundled Tk 8.6: https://www.python.org/download/mac/tcltk/ (this is what learners will prob use on Mac)

    I've tried to run it and here's what I have on my system:

        ~/d/t/chat (master) » python3.7 client.py
        Traceback (most recent call last):
          File "client.py", line 7, in <module>
            from tkinter import *
          File "/opt/local/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/tkinter/__init__.py", line 36, in <module>
            import _tkinter # If this fails your Python may not be configured for Tk
        ModuleNotFoundError: No module named '_tkinter'

    How about we write the tutorial and implement terminal clients first. Then we can have two branches of the tutorial -- one implementing a Tk client, one implementing a web client?

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Oct 7, 2018

    A CLI client is a necessary step along the way anyway, so that sounds good by me.

    You suggested:

    I'd organize the tutorial in a dedicated directory like "Doc/library/asyncio-tutorial/"

    I had a look at the source tree, there is an existing "howto" directory; do you still prefer your suggestion of using "Doc/library/" over something like "Doc/howto/asyncio.rst"?

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Oct 7, 2018

    I set up a basic structure under "Doc/library/asyncio-tutorial" as suggested, and opened a PR to show you how that looks. When I make
    more progress on a section, I'll post an update here.

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Oct 7, 2018

    I tested the Python 3.7.0 release version for Mac, the download called "macOS 64-bit installer" with checksum ae0717a02efea3b0eb34aadc680dc498 on this page:

    https://www.python.org/downloads/release/python-370/

    I downloaded, installed that on a mac, and the chat client app launched no problem. (I added a screenshot to my github repo readme).

    Your error "ModuleNotFoundError: No module named '_tkinter'" suggests that the python you were using is a different one, because in the release download, Tk 8.6.8 is bundled and the _tkinter module was built against it and must exist.

    Anyway, I just wanted to check that it does work on mac :)

    @ned-deily
    Copy link
    Member

    Yury, based on the file paths, you appear to be running a MacPorts python3.7. Like many other third-party distributors (but unlike the python.org installers), MacPorts separates Tkinter support into a separately-installable component. Try:

    port install py37-tkinter

    @1st1
    Copy link
    Member

    1st1 commented Oct 7, 2018

    Yury, based on the file paths, you appear to be running a MacPorts python3.7. Like many other third-party distributors (but unlike the python.org installers), MacPorts separates Tkinter support into a separately-installable component. Try:

    Thanks, Ned! Yeah, I know why my python37 doesn't have Tk and how to fix it. It's just I don't think the tutorial should require some component that we know is missing when python is installed through certain channels (with default flags/config).

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Oct 21, 2018

    I've added a few ideas for items in the "cookbook" page, which you'll see in the PR. If anyone has suggestions for more or better cookbook entries (recipes?), feel free to mention here or in the PR, I check both places. I expect to get more time to work on this next weekend, so it would be great to get ideas and reviews in during the week.

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Jan 6, 2019

    A quick note to say I have not abandoned this, it's just that life got complicated for me in late 2018. I intend to pick up progress again within the next month or two.

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Jun 15, 2019

    That was an long two months, apologies. I've made some fixes based on review comments and cleaned up some more of the code samples. The primary outstanding pieces are the client component of the chat application case study, and the GUI integration section of the client case study.

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Jun 16, 2019

    FYI I'm going to be using the 3rd-party prompt-toolkit for the chat client. (The server depends only on asyncio only). I put several hours research into finding a way for the CLI chat client to be not terrible, but it gets very complicated trying to manage stdin and stdout with asyncio. OTOH prompt-toolkit just gives us exactly what we need, and the code looks short, neat and easy to understand. I hope that's ok (that I'll be mentioning a 3rd party lib in the tutorial).

    @cjrh
    Copy link
    Mannequin Author

    cjrh mannequin commented Jun 19, 2019

    I'm removing the GUI section of the chat case study. Yury was right, it's not going to add anything useful. The CLI chat client will work well because prompt-toolkit has actual support for asyncio. Tkinter does not, and I think it'll be better to add a GUI section to this tutorial only once Tkinter gets first-class support for asyncio.

    @ezio-melotti ezio-melotti transferred this issue from another repository Apr 10, 2022
    @cjrh
    Copy link
    Contributor

    cjrh commented Apr 17, 2022

    So, I basically stalled on this work because I was waiting for changes in asyncio to settle down. In particular task groups and PEP 654 exception groups. After PEP 654 in 3.11, there will be more changes to asyncio to make use of it yes?

    @cjrh
    Copy link
    Contributor

    cjrh commented Apr 17, 2022

    Ok I see they're already in #90908

    @gvanrossum
    Copy link
    Member

    I doubt that asyncio will ever "settle down". :-) But anyway, I am currently working on docs for task groups.

    @kumaraditya303
    Copy link
    Contributor

    The PR #9748 seems stuck and the PR is very large, it would be better to split it up so that things get merged as ready.

    @CAM-Gerlach Are you interested in taking over this? I and hopefully @gvanrossum can volunteer to review the docs updates. It would be great to have this in 3.12.

    @cjrh
    Copy link
    Contributor

    cjrh commented Sep 29, 2022

    Anyone is more than welcome to take it over. Unfortunately I haven't been able to find bandwidth to pick this up again.

    @CAM-Gerlach
    Copy link
    Member

    This looks like a pretty big project, and as a disclaimer I have very little subject matter expertise or experience (coming from scientific Python, when I work with parallelism is almost invariably of the "embarrassingly parallel" compute-bound variety, and consequently use of asyncio is virtually unheard of in most of my discipline). Like with sqlite3, the other individual module I've spent a relatively disproportionate amount of time helping with the docs on, its something where most of what I do know comes from reviewing people's docs PRs on the subject (since at least in my domains of research, scientists tend to have a strong aversion to SQL-type databases as a data storage mechanism).

    This can actually be a significant benefit to helping ensure the docs, especially a generally beginner-focused tutorial, is easy to understand and follow, and doesn't embed assumptions that can be really easy to forget about as a subject matter expert (as I do myself when I am in those shoes). However, for the parts that aren't already written, I'm going to need knowledge help actually drafting something, at which point I can learn the material myself and then make it better (which is typically what I am strongest at anyway). The sprints next week might be a great time to work on it if others have some bandwidth to help guide me and answer my inevitable questions, assuming I am not called away to more immediately pressing tasks.

    My initial high level comments is based on the outline above, as well as skimming through the PR. Right now, it looks like much if not most of this content, aside from the "case study", is actually not quite a tutorial at all, at least as currently framed:

    • Most of the "Why" section sounds a lot more like archetypal "explanation"-type content that is oriented toward understanding and serves an important need, but is abstract rather than concrete ("why" rather than "how") and separate from the goal of getting a newcomer jump into using asyncio.
    • The "def vs async def" section sounds mostly like reference material describing the differences between the two types of functions, perhaps mixed with some explanation in parts, rather than guiding a newcomer through a series of steps to complete a learning task
    • "How to" run async def functions, and "dealing with concurrent tasks" could potentially be incorporated into a tutorial assuming it is basic enough and appropriate to the tutorial's topic, but as framed as a top-level section, it is, well, a how-to, not a tutorial, as it focuses on telling the user step by step how to solve specific problems.
    • The Case Study, however, sounds like a solid basis for a great tutorial—it sounds fun, engaging, achievable by a beginner but also realistic and a significant accomplishment that likely exercises a good amount of asyncio's functionality. Furthermore, it seems its primary focus is on helping the user learn the basics of using asyncio through walking them through an appropriate example project.

    Following this lens, each of these can be independent pages, or at least section of existing pages, and organized appropriately with other content that has a similar goal (and, ideally, in the order readers tend to refer to them, i.e. essentially the inverse of what they are now—i.e. something like Tutorial, How-To, Reference, Explanation (rather than the current opposite). However, that provides a great framework for splitting up the existing PR into more manageable and independent chunks that we could complete, revise and merge in series or in parallel, rather than a single large, interdependent set of documents. Its also a great opportunity for us to continue gaining more practical experience applying the Diataxis principles, as part of a broader process of of expanding and revising our documentation to better serve users' needs.

    @gvanrossum
    Copy link
    Member

    I'm happy to answer all sorts of questions, both in person during the sprint and online afterwards.

    I can't tell from skimming this issue how much was already written. Assuming not much has been written, my recommendation would be to start small, trying to get a PR merged that adds a very skinny tutorial that doesn't cover most topics (even important ones). That way we can hopefully spread the effort, perhaps similarly to how the Python tutorial grew from utter basics to what it is now, through many contributions.

    I hope we can make this a showcase for Diataxis (I hope you did the workshop, I didn't, alas, but I've skimmed the docs about it).

    @CAM-Gerlach
    Copy link
    Member

    I'm happy to answer all sorts of questions, both in person during the sprint and online afterwards.

    You've been really helpful answering my questions so far (including those I didn't know ask, but appreciated having answered) so I'm happy to have you on board!

    I can't tell from skimming this issue how much was already written.

    It wasn't formally linked here, but it seems from the corresponding PR, #9748 , that the great majority it was already written, except for the chat client CLI tutorial (aside from just a stub), and a few specific how-tos in the "cookbook" which were marked as TODO.

    Assuming not much has been written, my recommendation would be to start small, trying to get a PR merged that adds a very skinny tutorial that doesn't cover most topics (even important ones).

    Since most of it is already written, what I had been thinking might work best pulling out each atomic top-level component of that, doing the necessary work to get it ready to go and submitting each for review as an independent PR (either as its own document, or as a section of an existing one as appropriate). We could simply defer the client tutorial for now as well as the still-TODO how-to items, at least until after this was all completed. Right now, the existing PR is fairly well enough organized into components that it shouldn't be too difficult to pull apart, with a bit of thought ahead of time and appropriate revisions.

    I hope we can make this a showcase for Diataxis

    Indeed, I hope so too—though, our primary goal is to serve users, with Diataxis being an effective means to that end.

    I hope you did the workshop

    I'd hope so too, considering I was one of the organizers 😆 and I also have some (very helpful) practical experience applying it alongside Ezio and Erlend to the sqlite3 module docs (with the tutorial being what we ended up having to spend the most time on, and making the most improvements too).

    I've skimmed the docs about it

    The docs are good on their own, but as I found out along with Ezio and Erlend, we only started to truly understand and appreciate Diataxis once we dug in and really started putting it into practice.

    @kumaraditya303 kumaraditya303 added the 3.12 bugs and security fixes label Oct 6, 2022
    @CAM-Gerlach
    Copy link
    Member

    Unfortunately, I didn't get the chance to make much progress on this at the sprint as I was very bogged down on other things. I do have a fair number of urgent things to get out of the way first, but hopefully this should go fairly well once I get to it, since most of the work is already done in the PR, with the most of the remainder being right within my wheelhouse.

    @kumaraditya303
    Copy link
    Contributor

    We should also consider adding some async generators examples in tutorial with current best practices.

    See also #100108

    @willingc
    Copy link
    Contributor

    Changing the status of this issue to TODO. The original PR was closed. Thanks @cjrh for your work on that (:wave: hope all is well). This would be a good in-person effort at a future sprint.

    @willingc
    Copy link
    Contributor

    There is current interest in #124668 for additional examples. See #124668 (comment) for additional context.

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
    Labels
    3.12 bugs and security fixes 3.13 bugs and security fixes 3.14 new features, bugs and security fixes docs Documentation in the Doc dir topic-asyncio type-feature A feature request or enhancement
    Projects
    Status: Todo
    Development

    No branches or pull requests

    9 participants