├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE │ └── adafruit_circuitpython_pr.md └── workflows │ ├── build.yml │ ├── failure-help-text.yml │ ├── release_gh.yml │ └── release_pypi.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .readthedocs.yaml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── LICENSES ├── CC-BY-4.0.txt ├── MIT.txt └── Unlicense.txt ├── README.rst ├── README.rst.license ├── asyncio ├── __init__.py ├── core.py ├── event.py ├── funcs.py ├── lock.py ├── stream.py ├── task.py └── traceback.py ├── docs ├── _static │ ├── favicon.ico │ └── favicon.ico.license ├── api.rst ├── api.rst.license ├── conf.py ├── examples.rst ├── examples.rst.license ├── index.rst ├── index.rst.license └── requirements.txt ├── examples ├── asyncio_displayio_button.py ├── asyncio_event_example.py ├── asyncio_serial_examples.py ├── asyncio_simpletest.py └── asyncio_usb_cdc_boot.py ├── optional_requirements.txt ├── pyproject.toml ├── requirements.txt └── ruff.toml /.gitattributes: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | .py text eol=lf 6 | .rst text eol=lf 7 | .txt text eol=lf 8 | .yaml text eol=lf 9 | .toml text eol=lf 10 | .license text eol=lf 11 | .md text eol=lf 12 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/adafruit_circuitpython_pr.md: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | Thank you for contributing! Before you submit a pull request, please read the following. 6 | 7 | Make sure any changes you're submitting are in line with the CircuitPython Design Guide, available here: https://docs.circuitpython.org/en/latest/docs/design_guide.html 8 | 9 | If your changes are to documentation, please verify that the documentation builds locally by following the steps found here: https://adafru.it/build-docs 10 | 11 | Before submitting the pull request, make sure you've run Pylint and Black locally on your code. You can do this manually or using pre-commit. Instructions are available here: https://adafru.it/check-your-code 12 | 13 | Please remove all of this text before submitting. Include an explanation or list of changes included in your PR, as well as, if applicable, a link to any related issues. 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Build CI 6 | 7 | on: [pull_request, push] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Run Build CI workflow 14 | uses: adafruit/workflows-circuitpython-libs/build@main 15 | with: 16 | package-prefix: "asyncio" 17 | -------------------------------------------------------------------------------- /.github/workflows/failure-help-text.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Scott Shawcroft for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Failure help text 6 | 7 | on: 8 | workflow_run: 9 | workflows: ["Build CI"] 10 | types: 11 | - completed 12 | 13 | jobs: 14 | post-help: 15 | runs-on: ubuntu-latest 16 | if: ${{ github.event.workflow_run.conclusion == 'failure' && github.event.workflow_run.event == 'pull_request' }} 17 | steps: 18 | - name: Post comment to help 19 | uses: adafruit/circuitpython-action-library-ci-failed@v1 20 | -------------------------------------------------------------------------------- /.github/workflows/release_gh.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: GitHub Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run GitHub Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-gh@main 17 | with: 18 | package-prefix: "asyncio" 19 | github-token: ${{ secrets.GITHUB_TOKEN }} 20 | upload-url: ${{ github.event.release.upload_url }} 21 | -------------------------------------------------------------------------------- /.github/workflows/release_pypi.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: PyPI Release Actions 6 | 7 | on: 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | upload-release-assets: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run PyPI Release CI workflow 16 | uses: adafruit/workflows-circuitpython-libs/release-pypi@main 17 | with: 18 | pypi-username: ${{ secrets.pypi_username }} 19 | pypi-password: ${{ secrets.pypi_password }} 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Kattni Rembor, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # Do not include files and directories created by your personal work environment, such as the IDE 6 | # you use, except for those already listed here. Pull requests including changes to this file will 7 | # not be accepted. 8 | 9 | # This .gitignore file contains rules for files generated by working with CircuitPython libraries, 10 | # including building Sphinx, testing with pip, and creating a virual environment, as well as the 11 | # MacOS and IDE-specific files generated by using MacOS in general, or the PyCharm or VSCode IDEs. 12 | 13 | # If you find that there are files being generated on your machine that should not be included in 14 | # your git commit, you should create a .gitignore_global file on your computer to include the 15 | # files created by your personal setup. To do so, follow the two steps below. 16 | 17 | # First, create a file called .gitignore_global somewhere convenient for you, and add rules for 18 | # the files you want to exclude from git commits. 19 | 20 | # Second, configure Git to use the exclude file for all Git repositories by running the 21 | # following via commandline, replacing "path/to/your/" with the actual path to your newly created 22 | # .gitignore_global file: 23 | # git config --global core.excludesfile path/to/your/.gitignore_global 24 | 25 | # CircuitPython-specific files 26 | *.mpy 27 | 28 | # Python-specific files 29 | __pycache__ 30 | *.pyc 31 | 32 | # Sphinx build-specific files 33 | _build 34 | 35 | # This file results from running `pip -e install .` in a local repository 36 | *.egg-info 37 | 38 | # Virtual environment-specific files 39 | .env 40 | .venv 41 | 42 | # MacOS-specific files 43 | *.DS_Store 44 | 45 | # IDE-specific files 46 | .idea 47 | .vscode 48 | *~ 49 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Justin Myers for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | repos: 6 | - repo: https://github.com/pre-commit/pre-commit-hooks 7 | rev: v4.5.0 8 | hooks: 9 | - id: check-yaml 10 | - id: end-of-file-fixer 11 | - id: trailing-whitespace 12 | - repo: https://github.com/astral-sh/ruff-pre-commit 13 | rev: v0.3.4 14 | hooks: 15 | - id: ruff-format 16 | - id: ruff 17 | args: ["--fix"] 18 | - repo: https://github.com/fsfe/reuse-tool 19 | rev: v3.0.1 20 | hooks: 21 | - id: reuse 22 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 ladyada for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | # Read the Docs configuration file 6 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 7 | 8 | # Required 9 | version: 2 10 | 11 | sphinx: 12 | configuration: docs/conf.py 13 | 14 | build: 15 | os: ubuntu-20.04 16 | tools: 17 | python: "3" 18 | 19 | python: 20 | install: 21 | - requirements: docs/requirements.txt 22 | - requirements: requirements.txt 23 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 7 | # Adafruit Community Code of Conduct 8 | 9 | ## Our Pledge 10 | 11 | In the interest of fostering an open and welcoming environment, we as 12 | contributors and leaders pledge to making participation in our project and 13 | our community a harassment-free experience for everyone, regardless of age, body 14 | size, disability, ethnicity, gender identity and expression, level or type of 15 | experience, education, socio-economic status, nationality, personal appearance, 16 | race, religion, or sexual identity and orientation. 17 | 18 | ## Our Standards 19 | 20 | We are committed to providing a friendly, safe and welcoming environment for 21 | all. 22 | 23 | Examples of behavior that contributes to creating a positive environment 24 | include: 25 | 26 | * Be kind and courteous to others 27 | * Using welcoming and inclusive language 28 | * Being respectful of differing viewpoints and experiences 29 | * Collaborating with other community members 30 | * Gracefully accepting constructive criticism 31 | * Focusing on what is best for the community 32 | * Showing empathy towards other community members 33 | 34 | Examples of unacceptable behavior by participants include: 35 | 36 | * The use of sexualized language or imagery and sexual attention or advances 37 | * The use of inappropriate images, including in a community member's avatar 38 | * The use of inappropriate language, including in a community member's nickname 39 | * Any spamming, flaming, baiting or other attention-stealing behavior 40 | * Excessive or unwelcome helping; answering outside the scope of the question 41 | asked 42 | * Discussion or promotion of activities or projects that intend or pose a risk of 43 | significant harm 44 | * Trolling, insulting/derogatory comments, and personal or political attacks 45 | * Promoting or spreading disinformation, lies, or conspiracy theories against 46 | a person, group, organisation, project, or community 47 | * Public or private harassment 48 | * Publishing others' private information, such as a physical or electronic 49 | address, without explicit permission 50 | * Other conduct which could reasonably be considered inappropriate 51 | 52 | The goal of the standards and moderation guidelines outlined here is to build 53 | and maintain a respectful community. We ask that you don’t just aim to be 54 | "technically unimpeachable", but rather try to be your best self. 55 | 56 | We value many things beyond technical expertise, including collaboration and 57 | supporting others within our community. Providing a positive experience for 58 | other community members can have a much more significant impact than simply 59 | providing the correct answer. 60 | 61 | ## Our Responsibilities 62 | 63 | Project leaders are responsible for clarifying the standards of acceptable 64 | behavior and are expected to take appropriate and fair corrective action in 65 | response to any instances of unacceptable behavior. 66 | 67 | Project leaders have the right and responsibility to remove, edit, or 68 | reject messages, comments, commits, code, issues, and other contributions 69 | that are not aligned to this Code of Conduct, or to ban temporarily or 70 | permanently any community member for other behaviors that they deem 71 | inappropriate, threatening, offensive, or harmful. 72 | 73 | ## Moderation 74 | 75 | Instances of behaviors that violate the Adafruit Community Code of Conduct 76 | may be reported by any member of the community. Community members are 77 | encouraged to report these situations, including situations they witness 78 | involving other community members. 79 | 80 | You may report in the following ways: 81 | 82 | In any situation, you may email . 83 | 84 | On the Adafruit Discord, you may send an open message from any channel 85 | to all Community Moderators by tagging @community moderators. You may 86 | also send an open message from any channel, or a direct message to 87 | any Community Moderator. 88 | 89 | Email and direct message reports will be kept confidential. 90 | 91 | In situations on Discord where the issue is particularly offensive, possibly 92 | illegal, requires immediate action, or violates the Discord terms of service, 93 | you should also report the message directly to [Discord](https://discord.com/safety). 94 | 95 | These are the steps for upholding our community’s standards of conduct. 96 | 97 | 1. Any member of the community may report any situation that violates the 98 | CircuitPython Community Code of Conduct. All reports will be reviewed and 99 | investigated. 100 | 2. If the behavior is a severe violation, the community member who 101 | committed the violation may be banned immediately, without warning. 102 | 3. Otherwise, moderators will first respond to such behavior with a warning. 103 | 4. Moderators follow a soft "three strikes" policy - the community member may 104 | be given another chance, if they are receptive to the warning and change their 105 | behavior. 106 | 5. If the community member is unreceptive or unreasonable when warned by a 107 | moderator, or the warning goes unheeded, they may be banned for a first or 108 | second offense. Repeated offenses will result in the community member being 109 | banned. 110 | 6. Disciplinary actions (warnings, bans, etc) for Code of Conduct violations apply 111 | to the platform where the violation occurred. However, depending on the severity 112 | of the violation, the disciplinary action may be applied across Adafruit's other 113 | community platforms. For example, a severe violation on the Adafruit Discord 114 | server may result in a ban on not only the Adafruit Discord server, but also on 115 | the Adafruit GitHub organisation, Adafruit Forums, Adafruit Twitter, etc. 116 | 117 | ## Scope 118 | 119 | This Code of Conduct and the enforcement policies listed above apply to all 120 | Adafruit Community venues. This includes but is not limited to any community 121 | spaces (both public and private), the entire Adafruit Discord server, and 122 | Adafruit GitHub repositories. Examples of Adafruit Community spaces include 123 | but are not limited to meet-ups, audio chats on the Adafruit Discord, or 124 | interaction at a conference. 125 | 126 | This Code of Conduct applies both within project spaces and in public spaces 127 | when an individual is representing the project or its community. As a community 128 | member, you are representing our community, and are expected to behave 129 | accordingly. 130 | 131 | ## Attribution 132 | 133 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), 134 | version 1.4, available on [contributor-covenant.org](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html), 135 | and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). 136 | 137 | For other projects adopting the Adafruit Community Code of 138 | Conduct, please contact the maintainers of those projects for enforcement. 139 | If you wish to use this code of conduct for your own project, consider 140 | explicitly mentioning your moderation policy or making a copy with your 141 | own moderation policy so as to avoid confusion. 142 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2021 Dan Halbert for Adafruit Industries 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. 22 | -------------------------------------------------------------------------------- /LICENSES/CC-BY-4.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Attribution 4.0 International Creative Commons Corporation 2 | ("Creative Commons") is not a law firm and does not provide legal services 3 | or legal advice. Distribution of Creative Commons public licenses does not 4 | create a lawyer-client or other relationship. Creative Commons makes its licenses 5 | and related information available on an "as-is" basis. Creative Commons gives 6 | no warranties regarding its licenses, any material licensed under their terms 7 | and conditions, or any related information. Creative Commons disclaims all 8 | liability for damages resulting from their use to the fullest extent possible. 9 | 10 | Using Creative Commons Public Licenses 11 | 12 | Creative Commons public licenses provide a standard set of terms and conditions 13 | that creators and other rights holders may use to share original works of 14 | authorship and other material subject to copyright and certain other rights 15 | specified in the public license below. The following considerations are for 16 | informational purposes only, are not exhaustive, and do not form part of our 17 | licenses. 18 | 19 | Considerations for licensors: Our public licenses are intended for use by 20 | those authorized to give the public permission to use material in ways otherwise 21 | restricted by copyright and certain other rights. Our licenses are irrevocable. 22 | Licensors should read and understand the terms and conditions of the license 23 | they choose before applying it. Licensors should also secure all rights necessary 24 | before applying our licenses so that the public can reuse the material as 25 | expected. Licensors should clearly mark any material not subject to the license. 26 | This includes other CC-licensed material, or material used under an exception 27 | or limitation to copyright. More considerations for licensors : wiki.creativecommons.org/Considerations_for_licensors 28 | 29 | Considerations for the public: By using one of our public licenses, a licensor 30 | grants the public permission to use the licensed material under specified 31 | terms and conditions. If the licensor's permission is not necessary for any 32 | reason–for example, because of any applicable exception or limitation to copyright–then 33 | that use is not regulated by the license. Our licenses grant only permissions 34 | under copyright and certain other rights that a licensor has authority to 35 | grant. Use of the licensed material may still be restricted for other reasons, 36 | including because others have copyright or other rights in the material. A 37 | licensor may make special requests, such as asking that all changes be marked 38 | or described. Although not required by our licenses, you are encouraged to 39 | respect those requests where reasonable. More considerations for the public 40 | : wiki.creativecommons.org/Considerations_for_licensees Creative Commons Attribution 41 | 4.0 International Public License 42 | 43 | By exercising the Licensed Rights (defined below), You accept and agree to 44 | be bound by the terms and conditions of this Creative Commons Attribution 45 | 4.0 International Public License ("Public License"). To the extent this Public 46 | License may be interpreted as a contract, You are granted the Licensed Rights 47 | in consideration of Your acceptance of these terms and conditions, and the 48 | Licensor grants You such rights in consideration of benefits the Licensor 49 | receives from making the Licensed Material available under these terms and 50 | conditions. 51 | 52 | Section 1 – Definitions. 53 | 54 | a. Adapted Material means material subject to Copyright and Similar Rights 55 | that is derived from or based upon the Licensed Material and in which the 56 | Licensed Material is translated, altered, arranged, transformed, or otherwise 57 | modified in a manner requiring permission under the Copyright and Similar 58 | Rights held by the Licensor. For purposes of this Public License, where the 59 | Licensed Material is a musical work, performance, or sound recording, Adapted 60 | Material is always produced where the Licensed Material is synched in timed 61 | relation with a moving image. 62 | 63 | b. Adapter's License means the license You apply to Your Copyright and Similar 64 | Rights in Your contributions to Adapted Material in accordance with the terms 65 | and conditions of this Public License. 66 | 67 | c. Copyright and Similar Rights means copyright and/or similar rights closely 68 | related to copyright including, without limitation, performance, broadcast, 69 | sound recording, and Sui Generis Database Rights, without regard to how the 70 | rights are labeled or categorized. For purposes of this Public License, the 71 | rights specified in Section 2(b)(1)-(2) are not Copyright and Similar Rights. 72 | 73 | d. Effective Technological Measures means those measures that, in the absence 74 | of proper authority, may not be circumvented under laws fulfilling obligations 75 | under Article 11 of the WIPO Copyright Treaty adopted on December 20, 1996, 76 | and/or similar international agreements. 77 | 78 | e. Exceptions and Limitations means fair use, fair dealing, and/or any other 79 | exception or limitation to Copyright and Similar Rights that applies to Your 80 | use of the Licensed Material. 81 | 82 | f. Licensed Material means the artistic or literary work, database, or other 83 | material to which the Licensor applied this Public License. 84 | 85 | g. Licensed Rights means the rights granted to You subject to the terms and 86 | conditions of this Public License, which are limited to all Copyright and 87 | Similar Rights that apply to Your use of the Licensed Material and that the 88 | Licensor has authority to license. 89 | 90 | h. Licensor means the individual(s) or entity(ies) granting rights under this 91 | Public License. 92 | 93 | i. Share means to provide material to the public by any means or process that 94 | requires permission under the Licensed Rights, such as reproduction, public 95 | display, public performance, distribution, dissemination, communication, or 96 | importation, and to make material available to the public including in ways 97 | that members of the public may access the material from a place and at a time 98 | individually chosen by them. 99 | 100 | j. Sui Generis Database Rights means rights other than copyright resulting 101 | from Directive 96/9/EC of the European Parliament and of the Council of 11 102 | March 1996 on the legal protection of databases, as amended and/or succeeded, 103 | as well as other essentially equivalent rights anywhere in the world. 104 | 105 | k. You means the individual or entity exercising the Licensed Rights under 106 | this Public License. Your has a corresponding meaning. 107 | 108 | Section 2 – Scope. 109 | 110 | a. License grant. 111 | 112 | 1. Subject to the terms and conditions of this Public License, the Licensor 113 | hereby grants You a worldwide, royalty-free, non-sublicensable, non-exclusive, 114 | irrevocable license to exercise the Licensed Rights in the Licensed Material 115 | to: 116 | 117 | A. reproduce and Share the Licensed Material, in whole or in part; and 118 | 119 | B. produce, reproduce, and Share Adapted Material. 120 | 121 | 2. Exceptions and Limitations. For the avoidance of doubt, where Exceptions 122 | and Limitations apply to Your use, this Public License does not apply, and 123 | You do not need to comply with its terms and conditions. 124 | 125 | 3. Term. The term of this Public License is specified in Section 6(a). 126 | 127 | 4. Media and formats; technical modifications allowed. The Licensor authorizes 128 | You to exercise the Licensed Rights in all media and formats whether now known 129 | or hereafter created, and to make technical modifications necessary to do 130 | so. The Licensor waives and/or agrees not to assert any right or authority 131 | to forbid You from making technical modifications necessary to exercise the 132 | Licensed Rights, including technical modifications necessary to circumvent 133 | Effective Technological Measures. For purposes of this Public License, simply 134 | making modifications authorized by this Section 2(a)(4) never produces Adapted 135 | Material. 136 | 137 | 5. Downstream recipients. 138 | 139 | A. Offer from the Licensor – Licensed Material. Every recipient of the Licensed 140 | Material automatically receives an offer from the Licensor to exercise the 141 | Licensed Rights under the terms and conditions of this Public License. 142 | 143 | B. No downstream restrictions. You may not offer or impose any additional 144 | or different terms or conditions on, or apply any Effective Technological 145 | Measures to, the Licensed Material if doing so restricts exercise of the Licensed 146 | Rights by any recipient of the Licensed Material. 147 | 148 | 6. No endorsement. Nothing in this Public License constitutes or may be construed 149 | as permission to assert or imply that You are, or that Your use of the Licensed 150 | Material is, connected with, or sponsored, endorsed, or granted official status 151 | by, the Licensor or others designated to receive attribution as provided in 152 | Section 3(a)(1)(A)(i). 153 | 154 | b. Other rights. 155 | 156 | 1. Moral rights, such as the right of integrity, are not licensed under this 157 | Public License, nor are publicity, privacy, and/or other similar personality 158 | rights; however, to the extent possible, the Licensor waives and/or agrees 159 | not to assert any such rights held by the Licensor to the limited extent necessary 160 | to allow You to exercise the Licensed Rights, but not otherwise. 161 | 162 | 2. Patent and trademark rights are not licensed under this Public License. 163 | 164 | 3. To the extent possible, the Licensor waives any right to collect royalties 165 | from You for the exercise of the Licensed Rights, whether directly or through 166 | a collecting society under any voluntary or waivable statutory or compulsory 167 | licensing scheme. In all other cases the Licensor expressly reserves any right 168 | to collect such royalties. 169 | 170 | Section 3 – License Conditions. 171 | 172 | Your exercise of the Licensed Rights is expressly made subject to the following 173 | conditions. 174 | 175 | a. Attribution. 176 | 177 | 1. If You Share the Licensed Material (including in modified form), You must: 178 | 179 | A. retain the following if it is supplied by the Licensor with the Licensed 180 | Material: 181 | 182 | i. identification of the creator(s) of the Licensed Material and any others 183 | designated to receive attribution, in any reasonable manner requested by the 184 | Licensor (including by pseudonym if designated); 185 | 186 | ii. a copyright notice; 187 | 188 | iii. a notice that refers to this Public License; 189 | 190 | iv. a notice that refers to the disclaimer of warranties; 191 | 192 | v. a URI or hyperlink to the Licensed Material to the extent reasonably practicable; 193 | 194 | B. indicate if You modified the Licensed Material and retain an indication 195 | of any previous modifications; and 196 | 197 | C. indicate the Licensed Material is licensed under this Public License, and 198 | include the text of, or the URI or hyperlink to, this Public License. 199 | 200 | 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner 201 | based on the medium, means, and context in which You Share the Licensed Material. 202 | For example, it may be reasonable to satisfy the conditions by providing a 203 | URI or hyperlink to a resource that includes the required information. 204 | 205 | 3. If requested by the Licensor, You must remove any of the information required 206 | by Section 3(a)(1)(A) to the extent reasonably practicable. 207 | 208 | 4. If You Share Adapted Material You produce, the Adapter's License You apply 209 | must not prevent recipients of the Adapted Material from complying with this 210 | Public License. 211 | 212 | Section 4 – Sui Generis Database Rights. 213 | 214 | Where the Licensed Rights include Sui Generis Database Rights that apply to 215 | Your use of the Licensed Material: 216 | 217 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, 218 | reuse, reproduce, and Share all or a substantial portion of the contents of 219 | the database; 220 | 221 | b. if You include all or a substantial portion of the database contents in 222 | a database in which You have Sui Generis Database Rights, then the database 223 | in which You have Sui Generis Database Rights (but not its individual contents) 224 | is Adapted Material; and 225 | 226 | c. You must comply with the conditions in Section 3(a) if You Share all or 227 | a substantial portion of the contents of the database. 228 | 229 | For the avoidance of doubt, this Section 4 supplements and does not replace 230 | Your obligations under this Public License where the Licensed Rights include 231 | other Copyright and Similar Rights. 232 | 233 | Section 5 – Disclaimer of Warranties and Limitation of Liability. 234 | 235 | a. Unless otherwise separately undertaken by the Licensor, to the extent possible, 236 | the Licensor offers the Licensed Material as-is and as-available, and makes 237 | no representations or warranties of any kind concerning the Licensed Material, 238 | whether express, implied, statutory, or other. This includes, without limitation, 239 | warranties of title, merchantability, fitness for a particular purpose, non-infringement, 240 | absence of latent or other defects, accuracy, or the presence or absence of 241 | errors, whether or not known or discoverable. Where disclaimers of warranties 242 | are not allowed in full or in part, this disclaimer may not apply to You. 243 | 244 | b. To the extent possible, in no event will the Licensor be liable to You 245 | on any legal theory (including, without limitation, negligence) or otherwise 246 | for any direct, special, indirect, incidental, consequential, punitive, exemplary, 247 | or other losses, costs, expenses, or damages arising out of this Public License 248 | or use of the Licensed Material, even if the Licensor has been advised of 249 | the possibility of such losses, costs, expenses, or damages. Where a limitation 250 | of liability is not allowed in full or in part, this limitation may not apply 251 | to You. 252 | 253 | c. The disclaimer of warranties and limitation of liability provided above 254 | shall be interpreted in a manner that, to the extent possible, most closely 255 | approximates an absolute disclaimer and waiver of all liability. 256 | 257 | Section 6 – Term and Termination. 258 | 259 | a. This Public License applies for the term of the Copyright and Similar Rights 260 | licensed here. However, if You fail to comply with this Public License, then 261 | Your rights under this Public License terminate automatically. 262 | 263 | b. Where Your right to use the Licensed Material has terminated under Section 264 | 6(a), it reinstates: 265 | 266 | 1. automatically as of the date the violation is cured, provided it is cured 267 | within 30 days of Your discovery of the violation; or 268 | 269 | 2. upon express reinstatement by the Licensor. 270 | 271 | c. For the avoidance of doubt, this Section 6(b) does not affect any right 272 | the Licensor may have to seek remedies for Your violations of this Public 273 | License. 274 | 275 | d. For the avoidance of doubt, the Licensor may also offer the Licensed Material 276 | under separate terms or conditions or stop distributing the Licensed Material 277 | at any time; however, doing so will not terminate this Public License. 278 | 279 | e. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. 280 | 281 | Section 7 – Other Terms and Conditions. 282 | 283 | a. The Licensor shall not be bound by any additional or different terms or 284 | conditions communicated by You unless expressly agreed. 285 | 286 | b. Any arrangements, understandings, or agreements regarding the Licensed 287 | Material not stated herein are separate from and independent of the terms 288 | and conditions of this Public License. 289 | 290 | Section 8 – Interpretation. 291 | 292 | a. For the avoidance of doubt, this Public License does not, and shall not 293 | be interpreted to, reduce, limit, restrict, or impose conditions on any use 294 | of the Licensed Material that could lawfully be made without permission under 295 | this Public License. 296 | 297 | b. To the extent possible, if any provision of this Public License is deemed 298 | unenforceable, it shall be automatically reformed to the minimum extent necessary 299 | to make it enforceable. If the provision cannot be reformed, it shall be severed 300 | from this Public License without affecting the enforceability of the remaining 301 | terms and conditions. 302 | 303 | c. No term or condition of this Public License will be waived and no failure 304 | to comply consented to unless expressly agreed to by the Licensor. 305 | 306 | d. Nothing in this Public License constitutes or may be interpreted as a limitation 307 | upon, or waiver of, any privileges and immunities that apply to the Licensor 308 | or You, including from the legal processes of any jurisdiction or authority. 309 | 310 | Creative Commons is not a party to its public licenses. Notwithstanding, Creative 311 | Commons may elect to apply one of its public licenses to material it publishes 312 | and in those instances will be considered the "Licensor." The text of the 313 | Creative Commons public licenses is dedicated to the public domain under the 314 | CC0 Public Domain Dedication. Except for the limited purpose of indicating 315 | that material is shared under a Creative Commons public license or as otherwise 316 | permitted by the Creative Commons policies published at creativecommons.org/policies, 317 | Creative Commons does not authorize the use of the trademark "Creative Commons" 318 | or any other trademark or logo of Creative Commons without its prior written 319 | consent including, without limitation, in connection with any unauthorized 320 | modifications to any of its public licenses or any other arrangements, understandings, 321 | or agreements concerning use of licensed material. For the avoidance of doubt, 322 | this paragraph does not form part of the public licenses. 323 | 324 | Creative Commons may be contacted at creativecommons.org. 325 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License Copyright (c) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice (including the next 11 | paragraph) shall be included in all copies or substantial portions of the 12 | Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF 19 | OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /LICENSES/Unlicense.txt: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute 4 | this software, either in source code form or as a compiled binary, for any 5 | purpose, commercial or non-commercial, and by any means. 6 | 7 | In jurisdictions that recognize copyright laws, the author or authors of this 8 | software dedicate any and all copyright interest in the software to the public 9 | domain. We make this dedication for the benefit of the public at large and 10 | to the detriment of our heirs and successors. We intend this dedication to 11 | be an overt act of relinquishment in perpetuity of all present and future 12 | rights to this software under copyright law. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS 17 | BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 18 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH 19 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, 20 | please refer to 21 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | 5 | .. image:: https://readthedocs.org/projects/adafruit-circuitpython-asyncio/badge/?version=latest 6 | :target: https://docs.circuitpython.org/projects/asyncio/en/latest/ 7 | :alt: Documentation Status 8 | 9 | 10 | .. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg 11 | :target: https://adafru.it/discord 12 | :alt: Discord 13 | 14 | 15 | .. image:: https://github.com/adafruit/Adafruit_CircuitPython_asyncio/workflows/Build%20CI/badge.svg 16 | :target: https://github.com/adafruit/Adafruit_CircuitPython_asyncio/actions 17 | :alt: Build Status 18 | 19 | 20 | .. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json 21 | :target: https://github.com/astral-sh/ruff 22 | :alt: Code Style: Ruff 23 | 24 | Cooperative multitasking and asynchronous I/O 25 | 26 | The code in this library is largely based on the 27 | `MicroPython uasyncio implementation `_. 28 | 29 | Dependencies 30 | ============= 31 | This driver depends on: 32 | 33 | * `Adafruit CircuitPython `_ 34 | * `Ticks `_ 35 | 36 | Please ensure all dependencies are available on the CircuitPython filesystem. 37 | This is easily achieved by downloading 38 | `the Adafruit library and driver bundle `_ 39 | or individual libraries can be installed using 40 | `circup `_. 41 | 42 | 43 | Installing from PyPI 44 | ===================== 45 | This library is meant to be a subset of the ` ``asyncio`` module in CPython 46 | `_, and will not be made available on PyPI. 47 | Use the CPython version instead. 48 | 49 | 50 | Installing to a Connected CircuitPython Device with Circup 51 | ========================================================== 52 | 53 | Make sure that you have ``circup`` installed in your Python environment. 54 | Install it with the following command if necessary: 55 | 56 | .. code-block:: shell 57 | 58 | pip3 install circup 59 | 60 | With ``circup`` installed and your CircuitPython device connected use the 61 | following command to install: 62 | 63 | .. code-block:: shell 64 | 65 | circup install asyncio 66 | 67 | Or the following command to update an existing version: 68 | 69 | .. code-block:: shell 70 | 71 | circup update 72 | 73 | Documentation 74 | ============= 75 | 76 | API documentation for this library can be found on `Read the Docs `_. 77 | 78 | For information on building library documentation, please check out 79 | `this guide `_. 80 | 81 | Contributing 82 | ============ 83 | 84 | Contributions are welcome! Please read our `Code of Conduct 85 | `_ 86 | before contributing to help this project stay welcoming. 87 | -------------------------------------------------------------------------------- /README.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | SPDX-FileCopyrightText: Copyright (c) 2021 Dan Halbert for Adafruit Industries 3 | SPDX-License-Identifier: MIT 4 | -------------------------------------------------------------------------------- /asyncio/__init__.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | # MicroPython asyncio module 7 | # MIT license; Copyright (c) 2019 Damien P. George 8 | # 9 | # CIRCUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | from .core import * 17 | 18 | # CIRCUITPY-CHANGE: use CircuitPython version 19 | __version__ = "0.0.0+auto.0" 20 | __repo__ = "https://github.com/Adafruit/Adafruit_CircuitPython_asyncio.git" 21 | 22 | _attrs = { 23 | "wait_for": "funcs", 24 | "wait_for_ms": "funcs", 25 | "gather": "funcs", 26 | "Event": "event", 27 | "ThreadSafeFlag": "event", 28 | "Lock": "lock", 29 | "open_connection": "stream", 30 | "start_server": "stream", 31 | "StreamReader": "stream", 32 | "StreamWriter": "stream", 33 | } 34 | 35 | 36 | # Lazy loader, effectively does: 37 | # global attr 38 | # from .mod import attr 39 | def __getattr__(attr): 40 | mod = _attrs.get(attr, None) 41 | if mod is None: 42 | raise AttributeError(attr) 43 | value = getattr(__import__(mod, None, None, True, 1), attr) 44 | globals()[attr] = value 45 | return value 46 | -------------------------------------------------------------------------------- /asyncio/core.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | # MicroPython asyncio module 7 | # MIT license; Copyright (c) 2019 Damien P. George 8 | # 9 | # # CIRCUITPY-CHANGE: use CircuitPython version 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | # CIRCUITPY-CHANGE: use our ticks library 17 | import select 18 | import sys 19 | 20 | from adafruit_ticks import ticks_add, ticks_diff 21 | from adafruit_ticks import ticks_ms as ticks 22 | 23 | # CIRCUITPY-CHANGE: CircuitPython traceback support 24 | try: 25 | from traceback import print_exception 26 | except: 27 | from .traceback import print_exception 28 | 29 | # Import TaskQueue and Task, preferring built-in C code over Python code 30 | try: 31 | from _asyncio import Task, TaskQueue 32 | # CIRCUITPY-CHANGE: more specific error checking 33 | except ImportError: 34 | from .task import Task, TaskQueue 35 | 36 | ################################################################################ 37 | # Exceptions 38 | 39 | 40 | # CIRCUITPY-CHANGE 41 | # Depending on the release of CircuitPython these errors may or may not 42 | # exist in the C implementation of `_asyncio`. However, when they 43 | # do exist, they must be preferred over the Python code. 44 | try: 45 | from _asyncio import CancelledError, InvalidStateError 46 | except (ImportError, AttributeError): 47 | class CancelledError(BaseException): 48 | """Injected into a task when calling `Task.cancel()`""" 49 | pass 50 | 51 | 52 | class InvalidStateError(Exception): 53 | """Can be raised in situations like setting a result value for a task object that already has a result value set.""" 54 | pass 55 | 56 | 57 | class TimeoutError(Exception): 58 | # CIRCUITPY-CHANGE: docstring 59 | """Raised when waiting for a task longer than the specified timeout.""" 60 | 61 | pass 62 | 63 | 64 | # Used when calling Loop.call_exception_handler 65 | _exc_context = {"message": "Task exception wasn't retrieved", "exception": None, "future": None} 66 | 67 | 68 | ################################################################################ 69 | # Sleep functions 70 | 71 | 72 | # "Yield" once, then raise StopIteration 73 | class SingletonGenerator: 74 | def __init__(self): 75 | self.state = None 76 | self.exc = StopIteration() 77 | 78 | def __iter__(self): 79 | return self 80 | 81 | # CIRCUITPY-CHANGE: provide await 82 | def __await__(self): 83 | return self 84 | 85 | def __next__(self): 86 | if self.state is not None: 87 | _task_queue.push(cur_task, self.state) 88 | self.state = None 89 | return None 90 | else: 91 | self.exc.__traceback__ = None 92 | raise self.exc 93 | 94 | 95 | # Pause task execution for the given time (integer in milliseconds, uPy extension) 96 | # Use a SingletonGenerator to do it without allocating on the heap 97 | def sleep_ms(t, sgen=SingletonGenerator()): 98 | # CIRCUITPY-CHANGE: doc 99 | """Sleep for *t* milliseconds. 100 | 101 | This is a MicroPython extension. 102 | 103 | Returns a coroutine. 104 | """ 105 | 106 | # CIRCUITPY-CHANGE: add debugging hint 107 | assert sgen.state is None, "Check for a missing `await` in your code" 108 | sgen.state = ticks_add(ticks(), max(0, t)) 109 | return sgen 110 | 111 | 112 | # Pause task execution for the given time (in seconds) 113 | def sleep(t): 114 | # CIRCUITPY-CHANGE: doc 115 | """Sleep for *t* seconds. 116 | 117 | Returns a coroutine. 118 | """ 119 | 120 | return sleep_ms(int(t * 1000)) 121 | 122 | 123 | # CIRCUITPY-CHANGE: see https://github.com/adafruit/Adafruit_CircuitPython_asyncio/pull/30 124 | ################################################################################ 125 | # "Never schedule" object" 126 | # Don't re-schedule the object that awaits _never(). 127 | # For internal use only. Some constructs, like `await event.wait()`, 128 | # work by NOT re-scheduling the task which calls wait(), but by 129 | # having some other task schedule it later. 130 | class _NeverSingletonGenerator: 131 | def __init__(self): 132 | self.state = None 133 | self.exc = StopIteration() 134 | 135 | def __iter__(self): 136 | return self 137 | 138 | def __await__(self): 139 | return self 140 | 141 | def __next__(self): 142 | if self.state is not None: 143 | self.state = None 144 | return None 145 | else: 146 | self.exc.__traceback__ = None 147 | raise self.exc 148 | 149 | def _never(sgen=_NeverSingletonGenerator()): 150 | # assert sgen.state is None, "Check for a missing `await` in your code" 151 | sgen.state = False 152 | return sgen 153 | 154 | 155 | ################################################################################ 156 | # Queue and poller for stream IO 157 | 158 | 159 | class IOQueue: 160 | def __init__(self): 161 | self.poller = select.poll() 162 | self.map = {} # maps id(stream) to [task_waiting_read, task_waiting_write, stream] 163 | 164 | def _enqueue(self, s, idx): 165 | if id(s) not in self.map: 166 | entry = [None, None, s] 167 | entry[idx] = cur_task 168 | self.map[id(s)] = entry 169 | self.poller.register(s, select.POLLIN if idx == 0 else select.POLLOUT) 170 | else: 171 | sm = self.map[id(s)] 172 | assert sm[idx] is None 173 | assert sm[1 - idx] is not None 174 | sm[idx] = cur_task 175 | self.poller.modify(s, select.POLLIN | select.POLLOUT) 176 | # Link task to this IOQueue so it can be removed if needed 177 | cur_task.data = self 178 | 179 | def _dequeue(self, s): 180 | del self.map[id(s)] 181 | self.poller.unregister(s) 182 | 183 | # CIRCUITPY-CHANGE: async 184 | async def queue_read(self, s): 185 | self._enqueue(s, 0) 186 | # CIRCUITPY-CHANGE: do not reschedule 187 | await _never() 188 | 189 | # CIRCUITPY-CHANGE: async 190 | async def queue_write(self, s): 191 | self._enqueue(s, 1) 192 | # CIRCUITPY-CHANGE: do not reschedule 193 | await _never() 194 | 195 | def remove(self, task): 196 | while True: 197 | del_s = None 198 | for k in self.map: # Iterate without allocating on the heap 199 | q0, q1, s = self.map[k] 200 | if q0 is task or q1 is task: 201 | del_s = s 202 | break 203 | if del_s is not None: 204 | self._dequeue(s) 205 | else: 206 | break 207 | 208 | def wait_io_event(self, dt): 209 | for s, ev in self.poller.ipoll(dt): 210 | sm = self.map[id(s)] 211 | # print('poll', s, sm, ev) 212 | if ev & ~select.POLLOUT and sm[0] is not None: 213 | # POLLIN or error 214 | _task_queue.push(sm[0]) 215 | sm[0] = None 216 | if ev & ~select.POLLIN and sm[1] is not None: 217 | # POLLOUT or error 218 | _task_queue.push(sm[1]) 219 | sm[1] = None 220 | if sm[0] is None and sm[1] is None: 221 | self._dequeue(s) 222 | elif sm[0] is None: 223 | self.poller.modify(s, select.POLLOUT) 224 | else: 225 | self.poller.modify(s, select.POLLIN) 226 | 227 | 228 | ################################################################################ 229 | # Main run loop 230 | 231 | 232 | # Ensure the awaitable is a task 233 | def _promote_to_task(aw): 234 | return aw if isinstance(aw, Task) else create_task(aw) 235 | 236 | 237 | # Create and schedule a new task from a coroutine 238 | def create_task(coro): 239 | # CIRCUITPY-CHANGE: doc 240 | """Create a new task from the given coroutine and schedule it to run. 241 | 242 | Returns the corresponding `Task` object. 243 | """ 244 | 245 | if not hasattr(coro, "send"): 246 | raise TypeError("coroutine expected") 247 | t = Task(coro, globals()) 248 | _task_queue.push(t) 249 | return t 250 | 251 | 252 | # Keep scheduling tasks until there are none left to schedule 253 | def run_until_complete(main_task=None): 254 | # CIRCUITPY-CHANGE: doc 255 | """Run the given *main_task* until it completes.""" 256 | 257 | global cur_task 258 | excs_all = (CancelledError, Exception) # To prevent heap allocation in loop 259 | excs_stop = (CancelledError, StopIteration) # To prevent heap allocation in loop 260 | while True: 261 | # Wait until the head of _task_queue is ready to run 262 | dt = 1 263 | while dt > 0: 264 | dt = -1 265 | t = _task_queue.peek() 266 | if t: 267 | # A task waiting on _task_queue; "ph_key" is time to schedule task at 268 | dt = max(0, ticks_diff(t.ph_key, ticks())) 269 | elif not _io_queue.map: 270 | # No tasks can be woken so finished running 271 | cur_task = None 272 | return 273 | # print('(poll {})'.format(dt), len(_io_queue.map)) 274 | _io_queue.wait_io_event(dt) 275 | 276 | # Get next task to run and continue it 277 | t = _task_queue.pop() 278 | cur_task = t 279 | try: 280 | # Continue running the coroutine, it's responsible for rescheduling itself 281 | exc = t.data 282 | if not exc: 283 | t.coro.send(None) 284 | else: 285 | # If the task is finished and on the run queue and gets here, then it 286 | # had an exception and was not await'ed on. Throwing into it now will 287 | # raise StopIteration and the code below will catch this and run the 288 | # call_exception_handler function. 289 | t.data = None 290 | t.coro.throw(exc) 291 | except excs_all as er: 292 | # Check the task is not on any event queue 293 | assert t.data is None 294 | # This task is done, check if it's the main task and then loop should stop 295 | if t is main_task: 296 | cur_task = None 297 | if isinstance(er, StopIteration): 298 | return er.value 299 | raise er 300 | if t.state: 301 | # Task was running but is now finished. 302 | waiting = False 303 | if t.state is True: 304 | # "None" indicates that the task is complete and not await'ed on (yet). 305 | t.state = None 306 | elif callable(t.state): 307 | # The task has a callback registered to be called on completion. 308 | t.state(t, er) 309 | t.state = False 310 | waiting = True 311 | else: 312 | # Schedule any other tasks waiting on the completion of this task. 313 | while t.state.peek(): 314 | _task_queue.push(t.state.pop()) 315 | waiting = True 316 | # "False" indicates that the task is complete and has been await'ed on. 317 | t.state = False 318 | if not waiting and not isinstance(er, excs_stop): 319 | # An exception ended this detached task, so queue it for later 320 | # execution to handle the uncaught exception if no other task retrieves 321 | # the exception in the meantime (this is handled by Task.throw). 322 | _task_queue.push(t) 323 | # Save return value of coro to pass up to caller. 324 | t.data = er 325 | elif t.state is None: 326 | # Task is already finished and nothing await'ed on the task, 327 | # so call the exception handler. 328 | 329 | # Save exception raised by the coro for later use. 330 | t.data = exc 331 | 332 | # Create exception context and call the exception handler. 333 | _exc_context["exception"] = exc 334 | _exc_context["future"] = t 335 | Loop.call_exception_handler(_exc_context) 336 | 337 | 338 | # Create a new task from a coroutine and run it until it finishes 339 | def run(coro): 340 | # CIRCUITPY-CHANGE: doc 341 | """Create a new task from the given coroutine and run it until it completes. 342 | 343 | Returns the value returned by *coro*. 344 | """ 345 | 346 | return run_until_complete(create_task(coro)) 347 | 348 | 349 | ################################################################################ 350 | # Event loop wrapper 351 | 352 | 353 | async def _stopper(): 354 | pass 355 | 356 | 357 | cur_task = None 358 | _stop_task = None 359 | 360 | 361 | class Loop: 362 | # CIRCUITPY-CHANGE: doc 363 | """Class representing the event loop""" 364 | 365 | _exc_handler = None 366 | 367 | def create_task(coro): 368 | # CIRCUITPY-CHANGE: doc 369 | """Create a task from the given *coro* and return the new `Task` object.""" 370 | 371 | return create_task(coro) 372 | 373 | def run_forever(): 374 | # CIRCUITPY-CHANGE: doc 375 | """Run the event loop until `Loop.stop()` is called.""" 376 | 377 | global _stop_task 378 | _stop_task = Task(_stopper(), globals()) 379 | run_until_complete(_stop_task) 380 | # TODO should keep running until .stop() is called, even if there're no tasks left 381 | 382 | def run_until_complete(aw): 383 | # CIRCUITPY-CHANGE: doc 384 | """Run the given *awaitable* until it completes. If *awaitable* is not a task then 385 | it will be promoted to one. 386 | """ 387 | 388 | return run_until_complete(_promote_to_task(aw)) 389 | 390 | def stop(): 391 | # CIRCUITPY-CHANGE: doc 392 | """Stop the event loop""" 393 | 394 | global _stop_task 395 | if _stop_task is not None: 396 | _task_queue.push(_stop_task) 397 | # If stop() is called again, do nothing 398 | _stop_task = None 399 | 400 | def close(): 401 | # CIRCUITPY-CHANGE: doc 402 | """Close the event loop.""" 403 | 404 | pass 405 | 406 | def set_exception_handler(handler): 407 | # CIRCUITPY-CHANGE: doc 408 | """Set the exception handler to call when a Task raises an exception that is not 409 | caught. The *handler* should accept two arguments: ``(loop, context)`` 410 | """ 411 | 412 | Loop._exc_handler = handler 413 | 414 | def get_exception_handler(): 415 | # CIRCUITPY-CHANGE: doc 416 | """Get the current exception handler. Returns the handler, or ``None`` if no 417 | custom handler is set. 418 | """ 419 | 420 | return Loop._exc_handler 421 | 422 | def default_exception_handler(loop, context): 423 | # CIRCUITPY-CHANGE: doc 424 | """The default exception handler that is called.""" 425 | 426 | # CIRCUITPY-CHANGE: use CircuitPython traceback printing 427 | exc = context["exception"] 428 | print_exception(None, exc, exc.__traceback__) 429 | 430 | def call_exception_handler(context): 431 | # CIRCUITPY-CHANGE: doc 432 | """Call the current exception handler. The argument *context* is passed through 433 | and is a dictionary containing keys: 434 | ``'message'``, ``'exception'``, ``'future'`` 435 | """ 436 | (Loop._exc_handler or Loop.default_exception_handler)(Loop, context) 437 | 438 | 439 | # The runq_len and waitq_len arguments are for legacy uasyncio compatibility 440 | def get_event_loop(runq_len=0, waitq_len=0): 441 | # CIRCUITPY-CHANGE: doc 442 | """Return the event loop used to schedule and run tasks. See `Loop`. Deprecated and will be removed later.""" 443 | 444 | return Loop 445 | 446 | # CIRCUITPY-CHANGE: added, to match CPython 447 | def get_running_loop(): 448 | """Return the event loop used to schedule and run tasks. See `Loop`.""" 449 | 450 | return Loop 451 | 452 | 453 | def get_event_loop(runq_len=0, waitq_len=0): 454 | # CIRCUITPY-CHANGE: doc 455 | """Return the event loop used to schedule and run tasks. See `Loop`. Deprecated and will be removed later.""" 456 | 457 | # CIRCUITPY-CHANGE 458 | return get_running_loop() 459 | 460 | def current_task(): 461 | # CIRCUITPY-CHANGE: doc 462 | """Return the `Task` object associated with the currently running task.""" 463 | 464 | if cur_task is None: 465 | raise RuntimeError("no running event loop") 466 | return cur_task 467 | 468 | 469 | def new_event_loop(): 470 | # CIRCUITPY-CHANGE: doc 471 | """Reset the event loop and return it. 472 | 473 | **NOTE**: Since MicroPython only has a single event loop, this function just resets 474 | the loop's state, it does not create a new one 475 | """ 476 | 477 | # CIRCUITPY-CHANGE: add _exc_context, cur_task 478 | global _task_queue, _io_queue, _exc_context, cur_task 479 | # TaskQueue of Task instances 480 | _task_queue = TaskQueue() 481 | # Task queue and poller for stream IO 482 | _io_queue = IOQueue() 483 | # CIRCUITPY-CHANGE: exception info 484 | cur_task = None 485 | _exc_context['exception'] = None 486 | _exc_context['future'] = None 487 | return Loop 488 | 489 | 490 | # Initialise default event loop 491 | new_event_loop() 492 | -------------------------------------------------------------------------------- /asyncio/event.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | # MicroPython asyncio module 7 | # MIT license; Copyright (c) 2019-2020 Damien P. George 8 | # 9 | # CIRCUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | from . import core 17 | 18 | 19 | # Event class for primitive events that can be waited on, set, and cleared 20 | class Event: 21 | # CIRCUITPY-CHANGE: doc 22 | """Create a new event which can be used to synchronize tasks. Events 23 | start in the cleared state. 24 | """ 25 | 26 | def __init__(self): 27 | self.state = False # False=unset; True=set 28 | self.waiting = core.TaskQueue() # Queue of Tasks waiting on completion of this event 29 | 30 | def is_set(self): 31 | # CIRCUITPY-CHANGE: doc 32 | """Returns ``True`` if the event is set, ``False`` otherwise.""" 33 | 34 | return self.state 35 | 36 | def set(self): 37 | # CIRCUITPY-CHANGE: doc 38 | """Set the event. Any tasks waiting on the event will be scheduled to run. 39 | """ 40 | 41 | # Event becomes set, schedule any tasks waiting on it 42 | # Note: This must not be called from anything except the thread running 43 | # the asyncio loop (i.e. neither hard or soft IRQ, or a different thread). 44 | while self.waiting.peek(): 45 | core._task_queue.push(self.waiting.pop()) 46 | self.state = True 47 | 48 | def clear(self): 49 | # CIRCUITPY-CHANGE: doc 50 | """Clear the event.""" 51 | 52 | self.state = False 53 | 54 | # CIRCUITPY-CHANGE: async 55 | async def wait(self): 56 | # CIRCUITPY-CHANGE: doc 57 | """Wait for the event to be set. If the event is already set then it returns 58 | immediately. 59 | """ 60 | 61 | if not self.state: 62 | # Event not set, put the calling task on the event's waiting queue 63 | self.waiting.push(core.cur_task) 64 | # Set calling task's data to the event's queue so it can be removed if needed 65 | core.cur_task.data = self.waiting 66 | # CIRCUITPY-CHANGE: use await; never reschedule 67 | await core._never() 68 | return True 69 | 70 | 71 | # CIRCUITPY: remove ThreadSafeFlag; non-standard extension. 72 | -------------------------------------------------------------------------------- /asyncio/funcs.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | 6 | # MicroPython asyncio module 7 | # MIT license; Copyright (c) 2019-2022 Damien P. George 8 | # 9 | # CIRCUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | from . import core 17 | 18 | 19 | async def _run(waiter, aw): 20 | try: 21 | result = await aw 22 | status = True 23 | except BaseException as er: 24 | result = None 25 | status = er 26 | if waiter.data is None: 27 | # The waiter is still waiting, cancel it. 28 | if waiter.cancel(): 29 | # Waiter was cancelled by us, change its CancelledError to an instance of 30 | # CancelledError that contains the status and result of waiting on aw. 31 | # If the wait_for task subsequently gets cancelled externally then this 32 | # instance will be reset to a CancelledError instance without arguments. 33 | waiter.data = core.CancelledError(status, result) 34 | 35 | async def wait_for(aw, timeout, sleep=core.sleep): 36 | # CIRCUITPY-CHANGE: doc 37 | """Wait for the *aw* awaitable to complete, but cancel if it takes longer 38 | than *timeout* seconds. If *aw* is not a task then a task will be created 39 | from it. 40 | 41 | If a timeout occurs, it cancels the task and raises ``asyncio.TimeoutError``: 42 | this should be trapped by the caller. 43 | 44 | Returns the return value of *aw*. 45 | """ 46 | 47 | aw = core._promote_to_task(aw) 48 | if timeout is None: 49 | return await aw 50 | 51 | # Run aw in a separate runner task that manages its exceptions. 52 | runner_task = core.create_task(_run(core.cur_task, aw)) 53 | 54 | try: 55 | # Wait for the timeout to elapse. 56 | await sleep(timeout) 57 | except core.CancelledError as er: 58 | # CIRCUITPY-CHANGE: more general fetching of exception arg 59 | status = er.args[0] if er.args else None 60 | if status is None: 61 | # This wait_for was cancelled externally, so cancel aw and re-raise. 62 | runner_task.cancel() 63 | raise er 64 | elif status is True: 65 | # aw completed successfully and cancelled the sleep, so return aw's result. 66 | return er.args[1] 67 | else: 68 | # aw raised an exception, propagate it out to the caller. 69 | raise status 70 | 71 | # The sleep finished before aw, so cancel aw and raise TimeoutError. 72 | runner_task.cancel() 73 | await runner_task 74 | raise core.TimeoutError 75 | 76 | 77 | def wait_for_ms(aw, timeout): 78 | # CIRCUITPY-CHANGE: doc 79 | """Similar to `wait_for` but *timeout* is an integer in milliseconds. 80 | 81 | This is a MicroPython extension. 82 | 83 | Returns a coroutine. 84 | """ 85 | 86 | return wait_for(aw, timeout, core.sleep_ms) 87 | 88 | 89 | class _Remove: 90 | @staticmethod 91 | def remove(t): 92 | pass 93 | 94 | 95 | # CIRCUITPY-CHANGE: async 96 | async def gather(*aws, return_exceptions=False): 97 | # CIRCUITPY-CHANGE: doc 98 | """Run all *aws* awaitables concurrently. Any *aws* that are not tasks 99 | are promoted to tasks. 100 | 101 | Returns a list of return values of all *aws* 102 | """ 103 | # CIRCUITPY-CHANGE: no awaitables, so nothing to gather 104 | if not aws: 105 | return [] 106 | 107 | def done(t, er): 108 | # Sub-task "t" has finished, with exception "er". 109 | nonlocal state 110 | if gather_task.data is not _Remove: 111 | # The main gather task has already been scheduled, so do nothing. 112 | # This happens if another sub-task already raised an exception and 113 | # woke the main gather task (via this done function), or if the main 114 | # gather task was cancelled externally. 115 | return 116 | elif not return_exceptions and not isinstance(er, StopIteration): 117 | # A sub-task raised an exception, indicate that to the gather task. 118 | state = er 119 | else: 120 | state -= 1 121 | if state: 122 | # Still some sub-tasks running. 123 | return 124 | # Gather waiting is done, schedule the main gather task. 125 | core._task_queue.push(gather_task) 126 | 127 | # Prepare the sub-tasks for the gather. 128 | # The `state` variable counts the number of tasks to wait for, and can be negative 129 | # if the gather should not run at all (because a task already had an exception). 130 | ts = [core._promote_to_task(aw) for aw in aws] 131 | state = 0 132 | for i in range(len(ts)): 133 | if ts[i].state is True: 134 | # Task is running, register the callback to call when the task is done. 135 | ts[i].state = done 136 | state += 1 137 | elif not ts[i].state: 138 | # Task finished already. 139 | if not isinstance(ts[i].data, StopIteration): 140 | # Task finished by raising an exception. 141 | if not return_exceptions: 142 | # Do not run this gather at all. 143 | state = -len(ts) 144 | else: 145 | # Task being waited on, gather not currently supported for this case. 146 | raise RuntimeError("can't gather") 147 | 148 | # Set the state for execution of the gather. 149 | gather_task = core.cur_task 150 | cancel_all = False 151 | 152 | # Wait for a sub-task to need attention (if there are any to wait for). 153 | if state > 0: 154 | gather_task.data = _Remove 155 | try: 156 | await core._never() 157 | except core.CancelledError as er: 158 | cancel_all = True 159 | state = er 160 | 161 | # Clean up tasks. 162 | for i in range(len(ts)): 163 | if ts[i].state is done: 164 | # Sub-task is still running, deregister the callback and cancel if needed. 165 | ts[i].state = True 166 | if cancel_all: 167 | ts[i].cancel() 168 | elif isinstance(ts[i].data, StopIteration): 169 | # Sub-task ran to completion, get its return value. 170 | ts[i] = ts[i].data.value 171 | elif return_exceptions: 172 | # Get the sub-task exception to return in the list of return values. 173 | ts[i] = ts[i].data 174 | elif isinstance(state, int): 175 | # Raise the sub-task exception, if there is not already an exception to raise. 176 | state = ts[i].data 177 | 178 | # Either this gather was cancelled, or one of the sub-tasks raised an exception with 179 | # return_exceptions==False, so reraise the exception here. 180 | if state: 181 | raise state 182 | 183 | # Return the list of return values of each sub-task. 184 | return ts 185 | -------------------------------------------------------------------------------- /asyncio/lock.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | # 6 | # MicroPython uasyncio module 7 | # MIT license; Copyright (c) 2019-2020 Damien P. George 8 | 9 | # CICUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | """ 16 | Locks 17 | ===== 18 | """ 19 | 20 | from . import core 21 | 22 | 23 | # Lock class for primitive mutex capability 24 | class Lock: 25 | # CIRCUITPY-CHANGE: doc 26 | """Create a new lock which can be used to coordinate tasks. Locks start in 27 | the unlocked state. 28 | 29 | In addition to the methods below, locks can be used in an ``async with`` 30 | statement. 31 | """ 32 | 33 | def __init__(self): 34 | # The state can take the following values: 35 | # - 0: unlocked 36 | # - 1: locked 37 | # - : unlocked but this task has been scheduled to acquire the lock next 38 | self.state = 0 39 | # Queue of Tasks waiting to acquire this Lock 40 | self.waiting = core.TaskQueue() 41 | 42 | def locked(self): 43 | # CIRCUITPY-CHANGE: doc 44 | """Returns ``True`` if the lock is locked, otherwise ``False``.""" 45 | 46 | return self.state == 1 47 | 48 | def release(self): 49 | # CIRCUITPY-CHANGE: doc 50 | """Release the lock. If any tasks are waiting on the lock then the next 51 | one in the queue is scheduled to run and the lock remains locked. Otherwise, 52 | no tasks are waiting and the lock becomes unlocked. 53 | """ 54 | 55 | if self.state != 1: 56 | raise RuntimeError("Lock not acquired") 57 | if self.waiting.peek(): 58 | # Task(s) waiting on lock, schedule next Task 59 | self.state = self.waiting.pop() 60 | core._task_queue.push(self.state) 61 | else: 62 | # No Task waiting so unlock 63 | self.state = 0 64 | 65 | # CIRCUITPY-CHANGE: async, since we don't use yield 66 | async def acquire(self): 67 | # CIRCUITPY-CHANGE: doc 68 | """Wait for the lock to be in the unlocked state and then lock it in an 69 | atomic way. Only one task can acquire the lock at any one time. 70 | """ 71 | 72 | if self.state != 0: 73 | # Lock unavailable, put the calling Task on the waiting queue 74 | self.waiting.push(core.cur_task) 75 | # Set calling task's data to the lock's queue so it can be removed if needed 76 | core.cur_task.data = self.waiting 77 | try: 78 | # CIRCUITPY-CHANGE await without rescheduling 79 | await core._never() 80 | except core.CancelledError as er: 81 | if self.state == core.cur_task: 82 | # Cancelled while pending on resume, schedule next waiting Task 83 | self.state = 1 84 | self.release() 85 | raise er 86 | # Lock available, set it as locked 87 | self.state = 1 88 | return True 89 | 90 | async def __aenter__(self): 91 | return await self.acquire() 92 | 93 | async def __aexit__(self, exc_type, exc, tb): 94 | return self.release() 95 | -------------------------------------------------------------------------------- /asyncio/stream.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | # 6 | # MicroPython uasyncio module 7 | # MIT license; Copyright (c) 2019-2020 Damien P. George 8 | # 9 | # CIRCUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | from . import core 17 | 18 | 19 | class Stream: 20 | #CIRCUITPY-CHANGE: doc 21 | """This represents a TCP stream connection. To minimise code this class 22 | implements both a reader and a writer, and both ``StreamReader`` and 23 | ``StreamWriter`` alias to this class. 24 | """ 25 | 26 | def __init__(self, s, e={}): 27 | self.s = s 28 | self.e = e 29 | self.out_buf = b"" 30 | 31 | def get_extra_info(self, v): 32 | #CIRCUITPY-CHANGE: doc 33 | """Get extra information about the stream, given by *v*. The valid 34 | values for *v* are: ``peername``. 35 | """ 36 | 37 | return self.e[v] 38 | 39 | def close(self): 40 | pass 41 | 42 | # CIRCUITPY-CHANGE: async 43 | async def wait_closed(self): 44 | # CIRCUITPY-CHANGE: doc 45 | """Wait for the stream to close. 46 | """ 47 | 48 | # TODO yield? 49 | self.s.close() 50 | 51 | # CIRCUITPY-CHANGE: async 52 | async def read(self, n): 53 | # CIRCUITPY-CHANGE: doc 54 | """Read up to *n* bytes and return them. 55 | """ 56 | 57 | await core._io_queue.queue_read(self.s) 58 | return self.s.read(n) 59 | 60 | # CIRCUITPY-CHANGE: async 61 | async def readinto(self, buf): 62 | """Read up to n bytes into *buf* with n being equal to the length of *buf* 63 | 64 | Return the number of bytes read into *buf* 65 | 66 | This is a MicroPython extension. 67 | """ 68 | 69 | # CIRCUITPY-CHANGE: await, not yield 70 | await core._io_queue.queue_read(self.s) 71 | return self.s.readinto(buf) 72 | 73 | # CIRCUITPY-CHANGE: async 74 | async def readexactly(self, n): 75 | # CIRCUITPY-CHANGE: doc 76 | """Read exactly *n* bytes and return them as a bytes object. 77 | 78 | Raises an ``EOFError`` exception if the stream ends before reading 79 | *n* bytes. 80 | """ 81 | 82 | r = b"" 83 | while n: 84 | # CIRCUITPY-CHANGE: await, not yield 85 | await core._io_queue.queue_read(self.s) 86 | r2 = self.s.read(n) 87 | if r2 is not None: 88 | if not len(r2): 89 | raise EOFError 90 | r += r2 91 | n -= len(r2) 92 | return r 93 | 94 | # CIRCUITPY-CHANGE: async 95 | async def readline(self): 96 | # CIRCUITPY-CHANGE: doc 97 | """Read a line and return it. 98 | """ 99 | 100 | l = b"" 101 | while True: 102 | # CIRCUITPY-CHANGE: await, not yield 103 | await core._io_queue.queue_read(self.s) 104 | l2 = self.s.readline() # may do multiple reads but won't block 105 | l += l2 106 | if not l2 or l[-1] == 10: # \n (check l in case l2 is str) 107 | return l 108 | 109 | def write(self, buf): 110 | # CIRCUITPY-CHANGE: doc 111 | """Accumulated *buf* to the output buffer. The data is only flushed when 112 | `Stream.drain` is called. It is recommended to call `Stream.drain` 113 | immediately after calling this function. 114 | """ 115 | if not self.out_buf: 116 | # Try to write immediately to the underlying stream. 117 | ret = self.s.write(buf) 118 | if ret == len(buf): 119 | return 120 | if ret is not None: 121 | buf = buf[ret:] 122 | self.out_buf += buf 123 | 124 | # CIRCUITPY-CHANGE: async 125 | async def drain(self): 126 | # CIRCUITPY-CHANGE: doc 127 | """Drain (write) all buffered output data out to the stream. 128 | """ 129 | 130 | mv = memoryview(self.out_buf) 131 | off = 0 132 | while off < len(mv): 133 | # CIRCUITPY-CHANGE: await, not yield 134 | await core._io_queue.queue_write(self.s) 135 | ret = self.s.write(mv[off:]) 136 | if ret is not None: 137 | off += ret 138 | self.out_buf = b"" 139 | 140 | 141 | # Stream can be used for both reading and writing to save code size 142 | StreamReader = Stream 143 | StreamWriter = Stream 144 | 145 | 146 | # Create a TCP stream connection to a remote host 147 | # CIRCUITPY-CHANGE: async 148 | async def open_connection(host, port, ssl=None, server_hostname=None): 149 | # CIRCUITPY-CHANGE: doc 150 | """Open a TCP connection to the given *host* and *port*. The *host* address will 151 | be resolved using `socket.getaddrinfo`, which is currently a blocking call. 152 | 153 | Returns a pair of streams: a reader and a writer stream. Will raise a socket-specific 154 | ``OSError`` if the host could not be resolved or if the connection could not be made. 155 | """ 156 | 157 | import socket 158 | 159 | from uerrno import EINPROGRESS 160 | 161 | ai = socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM)[0] # TODO this is blocking! 162 | s = socket.socket(ai[0], ai[1], ai[2]) 163 | s.setblocking(False) 164 | try: 165 | s.connect(ai[-1]) 166 | except OSError as er: 167 | if er.errno != EINPROGRESS: 168 | raise er 169 | # wrap with SSL, if requested 170 | if ssl: 171 | if ssl is True: 172 | import ssl as _ssl 173 | 174 | ssl = _ssl.SSLContext(_ssl.PROTOCOL_TLS_CLIENT) 175 | if not server_hostname: 176 | server_hostname = host 177 | s = ssl.wrap_socket(s, server_hostname=server_hostname, do_handshake_on_connect=False) 178 | s.setblocking(False) 179 | ss = Stream(s) 180 | await core._io_queue.queue_write(s) 181 | return ss, ss 182 | 183 | 184 | # Class representing a TCP stream server, can be closed and used in "async with" 185 | class Server: 186 | # CIRCUITPY-CHANGE: doc 187 | """This represents the server class returned from `start_server`. It can be used in 188 | an ``async with`` statement to close the server upon exit. 189 | """ 190 | 191 | async def __aenter__(self): 192 | return self 193 | 194 | async def __aexit__(self, exc_type, exc, tb): 195 | self.close() 196 | await self.wait_closed() 197 | 198 | def close(self): 199 | # CIRCUITPY-CHANGE: doc 200 | """Close the server.""" 201 | 202 | self.task.cancel() 203 | 204 | async def wait_closed(self): 205 | """Wait for the server to close. 206 | """ 207 | 208 | await self.task 209 | 210 | async def _serve(self, s, cb, ssl): 211 | self.state = False 212 | # Accept incoming connections 213 | while True: 214 | try: 215 | # CIRCUITPY-CHANGE: await, not yield 216 | await core._io_queue.queue_read(s) 217 | except core.CancelledError as er: 218 | # The server task was cancelled, shutdown server and close socket. 219 | s.close() 220 | if self.state: 221 | # If the server was explicitly closed, ignore the cancellation. 222 | return 223 | else: 224 | # Otherwise e.g. the parent task was cancelled, propagate 225 | # cancellation. 226 | raise er 227 | try: 228 | s2, addr = s.accept() 229 | except: 230 | # Ignore a failed accept 231 | continue 232 | if ssl: 233 | try: 234 | s2 = ssl.wrap_socket(s2, server_side=True, do_handshake_on_connect=False) 235 | except OSError as e: 236 | core.sys.print_exception(e) 237 | s2.close() 238 | continue 239 | s2.setblocking(False) 240 | s2s = Stream(s2, {"peername": addr}) 241 | core.create_task(cb(s2s, s2s)) 242 | 243 | 244 | # Helper function to start a TCP stream server, running as a new task 245 | # TODO could use an accept-callback on socket read activity instead of creating a task 246 | async def start_server(cb, host, port, backlog=5): 247 | # CIRCUITPY-CHANGE: doc 248 | """Start a TCP server on the given *host* and *port*. The *cb* callback will be 249 | called with incoming, accepted connections, and be passed 2 arguments: reader 250 | writer streams for the connection. 251 | 252 | Returns a `Server` object. 253 | """ 254 | 255 | import socket 256 | 257 | # Create and bind server socket. 258 | host = socket.getaddrinfo(host, port)[0] # TODO this is blocking! 259 | s = socket.socket() 260 | s.setblocking(False) 261 | s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 262 | s.bind(host[-1]) 263 | s.listen(backlog) 264 | 265 | # Create and return server object and task. 266 | srv = Server() 267 | srv.task = core.create_task(srv._serve(s, cb, ssl)) 268 | try: 269 | # Ensure that the _serve task has been scheduled so that it gets to 270 | # handle cancellation. 271 | await core.sleep_ms(0) 272 | except core.CancelledError as er: 273 | # If the parent task is cancelled during this first sleep, then 274 | # we will leak the task and it will sit waiting for the socket, so 275 | # cancel it. 276 | srv.task.cancel() 277 | raise er 278 | return srv 279 | 280 | 281 | ################################################################################ 282 | # Legacy uasyncio compatibility 283 | 284 | 285 | async def stream_awrite(self, buf, off=0, sz=-1): 286 | if off != 0 or sz != -1: 287 | buf = memoryview(buf) 288 | if sz == -1: 289 | sz = len(buf) 290 | buf = buf[off : off + sz] 291 | self.write(buf) 292 | await self.drain() 293 | 294 | 295 | Stream.aclose = Stream.wait_closed 296 | Stream.awrite = stream_awrite 297 | Stream.awritestr = stream_awrite # TODO explicitly convert to bytes? 298 | -------------------------------------------------------------------------------- /asyncio/task.py: -------------------------------------------------------------------------------- 1 | # CIRCUITPY-CHANGE: SPDX 2 | # SPDX-FileCopyrightText: 2019-2020 Damien P. George 3 | # 4 | # SPDX-License-Identifier: MIT 5 | # 6 | # MicroPython uasyncio module 7 | # MIT license; Copyright (c) 2019-2020 Damien P. George 8 | # 9 | # CIRCUITPY-CHANGE 10 | # This code comes from MicroPython, and has not been run through black or pylint there. 11 | # Altering these files significantly would make merging difficult, so we will not use 12 | # pylint or black. 13 | # pylint: skip-file 14 | # fmt: off 15 | 16 | # This file contains the core TaskQueue based on a pairing heap, and the core Task class. 17 | # They can optionally be replaced by C implementations. 18 | 19 | from . import core 20 | 21 | 22 | # pairing-heap meld of 2 heaps; O(1) 23 | def ph_meld(h1, h2): 24 | if h1 is None: 25 | return h2 26 | if h2 is None: 27 | return h1 28 | lt = core.ticks_diff(h1.ph_key, h2.ph_key) < 0 29 | if lt: 30 | if h1.ph_child is None: 31 | h1.ph_child = h2 32 | else: 33 | h1.ph_child_last.ph_next = h2 34 | h1.ph_child_last = h2 35 | h2.ph_next = None 36 | h2.ph_rightmost_parent = h1 37 | return h1 38 | else: 39 | h1.ph_next = h2.ph_child 40 | h2.ph_child = h1 41 | if h1.ph_next is None: 42 | h2.ph_child_last = h1 43 | h1.ph_rightmost_parent = h2 44 | return h2 45 | 46 | 47 | # pairing-heap pairing operation; amortised O(log N) 48 | def ph_pairing(child): 49 | heap = None 50 | while child is not None: 51 | n1 = child 52 | child = child.ph_next 53 | n1.ph_next = None 54 | if child is not None: 55 | n2 = child 56 | child = child.ph_next 57 | n2.ph_next = None 58 | n1 = ph_meld(n1, n2) 59 | heap = ph_meld(heap, n1) 60 | return heap 61 | 62 | 63 | # pairing-heap delete of a node; stable, amortised O(log N) 64 | def ph_delete(heap, node): 65 | if node is heap: 66 | child = heap.ph_child 67 | node.ph_child = None 68 | return ph_pairing(child) 69 | # Find parent of node 70 | parent = node 71 | while parent.ph_next is not None: 72 | parent = parent.ph_next 73 | parent = parent.ph_rightmost_parent 74 | # Replace node with pairing of its children 75 | if node is parent.ph_child and node.ph_child is None: 76 | parent.ph_child = node.ph_next 77 | node.ph_next = None 78 | return heap 79 | elif node is parent.ph_child: 80 | child = node.ph_child 81 | next = node.ph_next 82 | node.ph_child = None 83 | node.ph_next = None 84 | node = ph_pairing(child) 85 | parent.ph_child = node 86 | else: 87 | n = parent.ph_child 88 | while node is not n.ph_next: 89 | n = n.ph_next 90 | child = node.ph_child 91 | next = node.ph_next 92 | node.ph_child = None 93 | node.ph_next = None 94 | node = ph_pairing(child) 95 | if node is None: 96 | node = n 97 | else: 98 | n.ph_next = node 99 | node.ph_next = next 100 | if next is None: 101 | node.ph_rightmost_parent = parent 102 | parent.ph_child_last = node 103 | return heap 104 | 105 | 106 | # TaskQueue class based on the above pairing-heap functions. 107 | class TaskQueue: 108 | def __init__(self): 109 | self.heap = None 110 | 111 | def peek(self): 112 | return self.heap 113 | 114 | def push(self, v, key=None): 115 | assert v.ph_child is None 116 | assert v.ph_next is None 117 | v.data = None 118 | v.ph_key = key if key is not None else core.ticks() 119 | self.heap = ph_meld(v, self.heap) 120 | 121 | def pop(self): 122 | v = self.heap 123 | assert v.ph_next is None 124 | self.heap = ph_pairing(v.ph_child) 125 | v.ph_child = None 126 | return v 127 | 128 | def remove(self, v): 129 | self.heap = ph_delete(self.heap, v) 130 | 131 | 132 | # Task class representing a coroutine, can be waited on and cancelled. 133 | class Task: 134 | # CIRCUITPY-CHANGE: doc 135 | """This object wraps a coroutine into a running task. Tasks can be waited on 136 | using ``await task``, which will wait for the task to complete and return the 137 | return value of the task. 138 | 139 | Tasks should not be created directly, rather use ``create_task`` to create them. 140 | """ 141 | 142 | def __init__(self, coro, globals=None): 143 | self.coro = coro # Coroutine of this Task 144 | self.data = None # General data for queue it is waiting on 145 | self.state = True # None, False, True, a callable, or a TaskQueue instance 146 | self.ph_key = 0 # Pairing heap 147 | self.ph_child = None # Paring heap 148 | self.ph_child_last = None # Paring heap 149 | self.ph_next = None # Paring heap 150 | self.ph_rightmost_parent = None # Paring heap 151 | 152 | def __iter__(self): 153 | if not self.state: 154 | # Task finished, signal that is has been await'ed on. 155 | self.state = False 156 | elif self.state is True: 157 | # Allocated head of linked list of Tasks waiting on completion of this task. 158 | self.state = TaskQueue() 159 | elif type(self.state) is not TaskQueue: 160 | # Task has state used for another purpose, so can't also wait on it. 161 | raise RuntimeError("can't wait") 162 | return self 163 | 164 | # CICUITPY-CHANGE: CircuitPython needs __await()__. 165 | __await__ = __iter__ 166 | 167 | def __next__(self): 168 | if not self.state: 169 | # CIRCUITPY-CHANGE 170 | if self.data is None: 171 | # Task finished but has already been sent to the loop's exception handler. 172 | raise StopIteration 173 | else: 174 | # Task finished, raise return value to caller so it can continue. 175 | raise self.data 176 | else: 177 | # Put calling task on waiting queue. 178 | self.state.push(core.cur_task) 179 | # Set calling task's data to this task that it waits on, to double-link it. 180 | core.cur_task.data = self 181 | 182 | def done(self): 183 | # CIRCUITPY-CHANGE: doc 184 | """Whether the task is complete.""" 185 | 186 | return not self.state 187 | 188 | def cancel(self): 189 | # CIRCUITPY-CHANGE: doc 190 | """Cancel the task by injecting a ``CancelledError`` into it. The task 191 | may or may not ignore this exception. 192 | """ 193 | 194 | # Check if task is already finished. 195 | if not self.state: 196 | return False 197 | # Can't cancel self (not supported yet). 198 | if self is core.cur_task: 199 | raise RuntimeError("can't cancel self") 200 | # If Task waits on another task then forward the cancel to the one it's waiting on. 201 | while isinstance(self.data, Task): 202 | self = self.data 203 | # Reschedule Task as a cancelled task. 204 | if hasattr(self.data, "remove"): 205 | # Not on the main running queue, remove the task from the queue it's on. 206 | self.data.remove(self) 207 | core._task_queue.push(self) 208 | elif core.ticks_diff(self.ph_key, core.ticks()) > 0: 209 | # On the main running queue but scheduled in the future, so bring it forward to now. 210 | core._task_queue.remove(self) 211 | core._task_queue.push(self) 212 | self.data = core.CancelledError 213 | return True 214 | -------------------------------------------------------------------------------- /asyncio/traceback.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 by Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | # 5 | 6 | # Note: not present in MicroPython asyncio 7 | 8 | """CircuitPython-specific traceback support for asyncio.""" 9 | 10 | try: 11 | from typing import List 12 | except ImportError: 13 | pass 14 | 15 | import sys 16 | 17 | 18 | def _print_traceback(traceback, limit=None, file=sys.stderr) -> List[str]: 19 | if limit is None: 20 | if hasattr(sys, "tracebacklimit"): 21 | limit = sys.tracebacklimit 22 | 23 | n = 0 24 | while traceback is not None: 25 | frame = traceback.tb_frame 26 | line_number = traceback.tb_lineno 27 | frame_code = frame.f_code 28 | filename = frame_code.co_filename 29 | name = frame_code.co_name 30 | print(f' File "{filename}", line {line_number}, in {name}', file=file) 31 | traceback = traceback.tb_next 32 | n = n + 1 33 | if limit is not None and n >= limit: 34 | break 35 | 36 | 37 | def print_exception(exception, value=None, traceback=None, limit=None, file=sys.stderr): 38 | """ 39 | Print exception information and stack trace to file. 40 | """ 41 | if traceback: 42 | print("Traceback (most recent call last):", file=file) 43 | _print_traceback(traceback, limit=limit, file=file) 44 | 45 | if isinstance(exception, BaseException): 46 | exception_type = type(exception).__name__ 47 | elif hasattr(exception, "__name__"): 48 | exception_type = exception.__name__ 49 | else: 50 | exception_type = type(value).__name__ 51 | 52 | valuestr = str(value) 53 | if value is None or not valuestr: 54 | print(exception_type, file=file) 55 | else: 56 | print(f"{str(exception_type)}: {valuestr}", file=file) 57 | -------------------------------------------------------------------------------- /docs/_static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_asyncio/c38458596707228122047112dc465225eb512549/docs/_static/favicon.ico -------------------------------------------------------------------------------- /docs/_static/favicon.ico.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2018 Phillip Torrone for Adafruit Industries 2 | 3 | SPDX-License-Identifier: CC-BY-4.0 4 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | :py:mod:`~asyncio` 2 | 3 | .. If you created a package, create one automodule per module in the package. 4 | 5 | .. If your library file(s) are nested in a directory (e.g. /adafruit_foo/foo.py) 6 | .. use this format as the module name: "adafruit_foo.foo" 7 | 8 | API Reference 9 | ############# 10 | 11 | .. automodule:: asyncio 12 | :members: 13 | 14 | .. automodule:: asyncio.core 15 | :members: 16 | :exclude-members: SingletonGenerator, IOQueue 17 | 18 | .. automodule:: asyncio.event 19 | :members: 20 | :exclude-members: ThreadSafeFlag 21 | 22 | .. automodule:: asyncio.funcs 23 | :members: 24 | 25 | .. automodule:: asyncio.lock 26 | :members: 27 | 28 | .. automodule:: asyncio.stream 29 | :members: 30 | :exclude-members: stream_awrite 31 | 32 | .. automodule:: asyncio.task 33 | :members: 34 | :exclude-members: ph_meld, ph_pairing, ph_delete, TaskQueue 35 | -------------------------------------------------------------------------------- /docs/api.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | SPDX-FileCopyrightText: Copyright (c) 2021 Dan Halbert for Adafruit Industries 3 | 4 | SPDX-License-Identifier: MIT 5 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | import datetime 6 | import os 7 | import sys 8 | 9 | sys.path.insert(0, os.path.abspath("..")) 10 | 11 | # -- General configuration ------------------------------------------------ 12 | 13 | # Add any Sphinx extension module names here, as strings. They can be 14 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 15 | # ones. 16 | extensions = [ 17 | "sphinx.ext.autodoc", 18 | "sphinxcontrib.jquery", 19 | "sphinx.ext.intersphinx", 20 | "sphinx.ext.napoleon", 21 | "sphinx.ext.todo", 22 | ] 23 | 24 | # TODO: Please Read! 25 | # Uncomment the below if you use native CircuitPython modules such as 26 | # digitalio, micropython and busio. List the modules you use. Without it, the 27 | # autodoc module docs will fail to generate with a warning. 28 | # autodoc_mock_imports = ["digitalio", "busio"] 29 | 30 | 31 | intersphinx_mapping = { 32 | "python": ("https://docs.python.org/3", None), 33 | "CircuitPython": ("https://docs.circuitpython.org/en/latest/", None), 34 | } 35 | 36 | # Show the docstring from both the class and its __init__() method. 37 | autoclass_content = "both" 38 | 39 | # Add any paths that contain templates here, relative to this directory. 40 | templates_path = ["_templates"] 41 | 42 | source_suffix = ".rst" 43 | 44 | # The master toctree document. 45 | master_doc = "index" 46 | 47 | # General information about the project. 48 | project = "Adafruit CircuitPython asyncio Library" 49 | creation_year = "2021" 50 | current_year = str(datetime.datetime.now().year) 51 | year_duration = ( 52 | current_year if current_year == creation_year else creation_year + " - " + current_year 53 | ) 54 | copyright = year_duration + " Dan Halbert" 55 | author = "Dan Halbert" 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = "1.0" 63 | # The full version, including alpha/beta/rc tags. 64 | release = "1.0" 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = "en" 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = [ 77 | "_build", 78 | "Thumbs.db", 79 | ".DS_Store", 80 | ".env", 81 | "CODE_OF_CONDUCT.md", 82 | ] 83 | 84 | # The reST default role (used for this markup: `text`) to use for all 85 | # documents. 86 | # 87 | default_role = "any" 88 | 89 | # If true, '()' will be appended to :func: etc. cross-reference text. 90 | # 91 | add_function_parentheses = True 92 | 93 | # The name of the Pygments (syntax highlighting) style to use. 94 | pygments_style = "sphinx" 95 | 96 | # If true, `todo` and `todoList` produce output, else they produce nothing. 97 | todo_include_todos = False 98 | 99 | # If this is True, todo emits a warning for each TODO entries. The default is False. 100 | todo_emit_warnings = True 101 | 102 | napoleon_numpy_docstring = False 103 | 104 | # -- Options for HTML output ---------------------------------------------- 105 | 106 | # The theme to use for HTML and HTML Help pages. See the documentation for 107 | # a list of builtin themes. 108 | # 109 | import sphinx_rtd_theme 110 | 111 | html_theme = "sphinx_rtd_theme" 112 | 113 | # Add any paths that contain custom static files (such as style sheets) here, 114 | # relative to this directory. They are copied after the builtin static files, 115 | # so a file named "default.css" will overwrite the builtin "default.css". 116 | html_static_path = ["_static"] 117 | 118 | # The name of an image file (relative to this directory) to use as a favicon of 119 | # the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 120 | # pixels large. 121 | # 122 | html_favicon = "_static/favicon.ico" 123 | 124 | # Output file base name for HTML help builder. 125 | htmlhelp_basename = "Adafruit_CircuitPython_Asyncio_Librarydoc" 126 | 127 | # -- Options for LaTeX output --------------------------------------------- 128 | 129 | latex_elements = { 130 | # The paper size ('letterpaper' or 'a4paper'). 131 | # 'papersize': 'letterpaper', 132 | # The font size ('10pt', '11pt' or '12pt'). 133 | # 'pointsize': '10pt', 134 | # Additional stuff for the LaTeX preamble. 135 | # 'preamble': '', 136 | # Latex figure (float) alignment 137 | # 'figure_align': 'htbp', 138 | } 139 | 140 | # Grouping the document tree into LaTeX files. List of tuples 141 | # (source start file, target name, title, 142 | # author, documentclass [howto, manual, or own class]). 143 | latex_documents = [ 144 | ( 145 | master_doc, 146 | "Adafruit_CircuitPython_asyncio_Library.tex", 147 | "Adafruit CircuitPython asyncio Library Documentation", 148 | author, 149 | "manual", 150 | ), 151 | ] 152 | 153 | # -- Options for manual page output --------------------------------------- 154 | 155 | # One entry per manual page. List of tuples 156 | # (source start file, name, description, authors, manual section). 157 | man_pages = [ 158 | ( 159 | master_doc, 160 | "Adafruit_CircuitPython_asyncio_Library", 161 | "Adafruit CircuitPython asyncio Library Documentation", 162 | [author], 163 | 1, 164 | ), 165 | ] 166 | 167 | # -- Options for Texinfo output ------------------------------------------- 168 | 169 | # Grouping the document tree into Texinfo files. List of tuples 170 | # (source start file, target name, title, author, 171 | # dir menu entry, description, category) 172 | texinfo_documents = [ 173 | ( 174 | master_doc, 175 | "Adafruit_CircuitPython_asyncio_Library", 176 | "Adafruit CircuitPython asyncio Library Documentation", 177 | author, 178 | "Adafruit_CircuitPython_asyncio_Library", 179 | "One line description of project.", 180 | "Miscellaneous", 181 | ), 182 | ] 183 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Simple test 2 | ------------ 3 | 4 | Ensure your device works with this simple test. 5 | 6 | .. literalinclude:: ../examples/asyncio_simpletest.py 7 | :caption: examples/asyncio_simpletest.py 8 | :linenos: 9 | 10 | Displayio Button Example 11 | ------------------------ 12 | 13 | Illustrates how to use Display_Buttons and blinking vectorio.Circles with asyncio 14 | 15 | .. literalinclude:: ../examples/asyncio_displayio_button.py 16 | :caption: examples/asyncio_displayio_button.py 17 | :linenos: 18 | -------------------------------------------------------------------------------- /docs/examples.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | SPDX-FileCopyrightText: Copyright (c) 2021 Dan Halbert for Adafruit Industries 3 | 4 | SPDX-License-Identifier: MIT 5 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | 2 | .. include:: ../README.rst 3 | 4 | Table of Contents 5 | ================= 6 | 7 | .. toctree:: 8 | :maxdepth: 4 9 | :hidden: 10 | 11 | self 12 | 13 | .. toctree:: 14 | :caption: Examples 15 | 16 | examples 17 | 18 | .. toctree:: 19 | :caption: API Reference 20 | :maxdepth: 3 21 | 22 | api 23 | 24 | .. toctree:: 25 | :caption: Tutorials 26 | 27 | .. toctree:: 28 | :caption: Related Products 29 | 30 | .. toctree:: 31 | :caption: Other Links 32 | 33 | Download from GitHub 34 | Download Library Bundle 35 | CircuitPython Reference Documentation 36 | CircuitPython Support Forum 37 | Discord Chat 38 | Adafruit Learning System 39 | Adafruit Blog 40 | Adafruit Store 41 | 42 | Indices and tables 43 | ================== 44 | 45 | * :ref:`genindex` 46 | * :ref:`modindex` 47 | * :ref:`search` 48 | -------------------------------------------------------------------------------- /docs/index.rst.license: -------------------------------------------------------------------------------- 1 | SPDX-FileCopyrightText: 2017 Scott Shawcroft, written for Adafruit Industries 2 | SPDX-FileCopyrightText: Copyright (c) 2021 Dan Halbert for Adafruit Industries 3 | 4 | SPDX-License-Identifier: MIT 5 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Kattni Rembor for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | sphinx 6 | sphinxcontrib-jquery 7 | sphinx-rtd-theme 8 | -------------------------------------------------------------------------------- /examples/asyncio_displayio_button.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Tim Cocks for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | """ 5 | Example that illustrates how to use Displayio Buttons to modify 6 | some blinking circles. One button inverts colors, the others change 7 | the interval length of the blink for one of the circles. 8 | """ 9 | 10 | import asyncio 11 | 12 | import adafruit_touchscreen 13 | import board 14 | import displayio 15 | import terminalio 16 | import vectorio 17 | from adafruit_button import Button 18 | 19 | # use built-in display 20 | display = board.DISPLAY 21 | 22 | # explicitly set the display to default orientation in-case it was changed 23 | display.rotation = 0 24 | 25 | # --| Button Config |------------------------------------------------- 26 | # invert color Button 27 | BUTTON_1_X = 10 28 | BUTTON_1_Y = 80 29 | BUTTON_1_LABEL = "Invert Color" 30 | 31 | # slower interval Button 32 | BUTTON_2_X = 200 33 | BUTTON_2_Y = 160 34 | BUTTON_2_LABEL = "Slower" 35 | 36 | # faster interval Button 37 | BUTTON_3_X = 200 38 | BUTTON_3_Y = 80 39 | BUTTON_3_LABEL = "Faster" 40 | 41 | # shared button configurations 42 | BUTTON_WIDTH = 100 43 | BUTTON_HEIGHT = 50 44 | BUTTON_STYLE = Button.ROUNDRECT 45 | BUTTON_FILL_COLOR = 0x00FFFF 46 | BUTTON_OUTLINE_COLOR = 0xFF00FF 47 | BUTTON_LABEL_COLOR = 0x000000 48 | # --| Button Config |------------------------------------------------- 49 | 50 | # Setup touchscreen (PyPortal) 51 | ts = adafruit_touchscreen.Touchscreen( 52 | board.TOUCH_XL, 53 | board.TOUCH_XR, 54 | board.TOUCH_YD, 55 | board.TOUCH_YU, 56 | calibration=((5200, 59000), (5800, 57000)), 57 | size=(display.width, display.height), 58 | ) 59 | 60 | # initialize color button 61 | invert_color_btn = Button( 62 | x=BUTTON_1_X, 63 | y=BUTTON_1_Y, 64 | width=BUTTON_WIDTH, 65 | height=BUTTON_HEIGHT, 66 | style=BUTTON_STYLE, 67 | fill_color=BUTTON_FILL_COLOR, 68 | outline_color=BUTTON_OUTLINE_COLOR, 69 | label=BUTTON_1_LABEL, 70 | label_font=terminalio.FONT, 71 | label_color=BUTTON_LABEL_COLOR, 72 | ) 73 | 74 | # initialize interval slower button 75 | interval_slower_btn = Button( 76 | x=BUTTON_2_X, 77 | y=BUTTON_2_Y, 78 | width=BUTTON_WIDTH, 79 | height=BUTTON_HEIGHT, 80 | style=BUTTON_STYLE, 81 | fill_color=BUTTON_FILL_COLOR, 82 | outline_color=BUTTON_OUTLINE_COLOR, 83 | label=BUTTON_2_LABEL, 84 | label_font=terminalio.FONT, 85 | label_color=BUTTON_LABEL_COLOR, 86 | ) 87 | 88 | # initialize interval faster button 89 | interval_faster_btn = Button( 90 | x=BUTTON_3_X, 91 | y=BUTTON_3_Y, 92 | width=BUTTON_WIDTH, 93 | height=BUTTON_HEIGHT, 94 | style=BUTTON_STYLE, 95 | fill_color=BUTTON_FILL_COLOR, 96 | outline_color=BUTTON_OUTLINE_COLOR, 97 | label=BUTTON_3_LABEL, 98 | label_font=terminalio.FONT, 99 | label_color=BUTTON_LABEL_COLOR, 100 | ) 101 | 102 | 103 | # Button state data object. Will hold either true of false whether button is currently pressed 104 | class ButtonState: 105 | def __init__(self, initial_state): 106 | self.state = initial_state 107 | 108 | 109 | # Interval length data object. Holds the amount of time in ms the interval should last for 110 | class Interval: 111 | def __init__(self, initial_value): 112 | self.value = initial_value 113 | 114 | 115 | # main group to show things on the display 116 | main_group = displayio.Group() 117 | 118 | # Initialize first circle 119 | palette_1 = displayio.Palette(2) 120 | palette_1[0] = 0x125690 121 | palette_1[1] = 0x125690 122 | circle_1 = vectorio.Circle(pixel_shader=palette_1, radius=15, x=20, y=20) 123 | 124 | # Initialize second circle 125 | palette_2 = displayio.Palette(2) 126 | palette_2[0] = 0x12FF30 127 | palette_2[1] = 0x12FF30 128 | circle_2 = vectorio.Circle(pixel_shader=palette_2, radius=15, x=60, y=20) 129 | 130 | # add everything to the group, so it gets displayed 131 | main_group.append(circle_1) 132 | main_group.append(circle_2) 133 | main_group.append(invert_color_btn) 134 | main_group.append(interval_slower_btn) 135 | main_group.append(interval_faster_btn) 136 | 137 | 138 | async def blink(palette, interval, count, button_state): # Don't forget the async! 139 | """ 140 | blink coroutine. Hides and shows a vectorio shape by 141 | using make_transparent() and make_opaque() on it's palette. 142 | 143 | :param displayio.Palette palette: The palette to change colors on for blinking 144 | :param Interval interval: The Interval data object containing the interval length to use 145 | :param int count: The number of times to repeat the blink. -1 for indefinite loop 146 | :param ButtonState button_state: The ButtonState data object for the invert color button 147 | """ 148 | while count < 0 or count > 0: 149 | # if the color button is pressed 150 | if button_state.state: 151 | # if the color is still on default 152 | if palette[0] == palette[1]: 153 | # invert the color by subtracting from white 154 | palette[0] = 0xFFFFFF - palette[0] 155 | 156 | # if the color button is not pressed 157 | else: 158 | # set the color back to default 159 | palette[0] = palette[1] 160 | 161 | # hide the circle 162 | palette.make_opaque(0) 163 | # wait interval length 164 | await asyncio.sleep(interval.value / 1000) # Don't forget the await! 165 | 166 | # show the circle 167 | palette.make_transparent(0) 168 | # wait interval length 169 | await asyncio.sleep(interval.value / 1000) # Don't forget the await! 170 | 171 | # decrement count if it's positive 172 | if count > 0: 173 | count -= 1 174 | 175 | 176 | def handle_color_button(touch_event, color_button, button_state): 177 | """ 178 | Check if the color button is pressed, and updates 179 | the ButtonState data object as appropriate 180 | 181 | :param touch_event: The touch point object from touchscreen 182 | :param Button color_button: The button to check for presses on 183 | :param ButtonState button_state: ButtonState data object to set 184 | the current value into 185 | """ 186 | 187 | # if there is a touch event 188 | if touch_event: 189 | # if the color button is being touched 190 | if color_button.contains(touch_event): 191 | # set selected to change button color 192 | color_button.selected = True 193 | # set button_state so other coroutines can access it 194 | button_state.state = True 195 | 196 | # the color button is not being touched 197 | else: 198 | # set selected to change button color back to default 199 | color_button.selected = False # if touch is dragged outside of button 200 | # set button_state so other coroutines can access it. 201 | button_state.state = False 202 | 203 | # there are no touch events 204 | elif color_button.selected: 205 | # set selected back to false to change button back to default color 206 | color_button.selected = False 207 | # set button_state so other coroutines can access it 208 | button_state.state = False 209 | 210 | 211 | def handle_interval_buttons(touch_event, button_slower, button_faster, interval): 212 | """ 213 | Will check for presses on 214 | the faster and slower buttons and updated the data in the 215 | Interval data object as appropriate 216 | 217 | :param touch_event: Touch point object from touchscreen 218 | :param Button button_slower: The slower button object 219 | :param Button button_faster: The faster button object 220 | :param Interval interval: The Interval data object to store state 221 | """ 222 | # if there are any touch events 223 | if touch_event: 224 | # if the slower button is being touched 225 | if button_slower.contains(touch_event): 226 | # if it just became pressed. i.e. was not pressed last frame 227 | if not button_slower.selected: 228 | # set selected to change the button color 229 | button_slower.selected = True 230 | 231 | # increment the interval length and store it on the data object 232 | interval.value += 100 233 | print(f"new interval val: {interval.value}") 234 | 235 | # if the slower button is not being touched 236 | else: 237 | # set selected to put the slower button back to default color 238 | button_slower.selected = False 239 | 240 | # if the faster button is being touched 241 | if button_faster.contains(touch_event): 242 | # if it just became pressed. i.e. was not pressed last frame 243 | if not button_faster.selected: 244 | # set selected to change the button color 245 | button_faster.selected = True 246 | # if the interval is large enough to decrement 247 | if interval.value >= 100: 248 | # decrement interval value and store it on the data object 249 | interval.value -= 100 250 | print(f"new interval val: {interval.value}") 251 | 252 | # if the faster button is not being touched 253 | else: 254 | # set selected back to false to change color back to default 255 | button_faster.selected = False 256 | 257 | # there are no touch events 258 | else: 259 | # if slower button is the pressed color 260 | if button_slower.selected: 261 | # set it back to default color 262 | button_slower.selected = False 263 | 264 | # if the faster button is pressed color 265 | if button_faster.selected: 266 | # set it back to default color 267 | button_faster.selected = False 268 | 269 | 270 | async def monitor_buttons(button_slower, button_faster, color_button, interval, button_state): 271 | """ 272 | monitor_buttons coroutine. 273 | 274 | :param Button button_slower: The slower button object 275 | :param Button button_faster: The faster button object 276 | :param Button color_button: The invert color button object 277 | :param Interval interval: The Interval data object to store state 278 | :param ButtonState button_state: The ButtonState data object to 279 | store color button state 280 | """ 281 | while True: 282 | # get current touch data from overlay 283 | p = ts.touch_point 284 | 285 | # handle touch event data 286 | handle_color_button(p, color_button, button_state) 287 | handle_interval_buttons(p, button_slower, button_faster, interval) 288 | 289 | # allow other tasks to do work 290 | await asyncio.sleep(0) 291 | 292 | 293 | # main coroutine 294 | async def main(): # Don't forget the async! 295 | # create data objects 296 | color_btn_state = ButtonState(False) 297 | interval_1 = Interval(550) 298 | interval_2 = Interval(350) 299 | 300 | # create circle blink tasks 301 | circle_1_task = asyncio.create_task(blink(palette_1, interval_1, -1, color_btn_state)) 302 | circle_2_task = asyncio.create_task(blink(palette_2, interval_2, 20, color_btn_state)) 303 | 304 | # create buttons task 305 | button_task = asyncio.create_task( 306 | monitor_buttons( 307 | interval_slower_btn, 308 | interval_faster_btn, 309 | invert_color_btn, 310 | interval_1, 311 | color_btn_state, 312 | ) 313 | ) 314 | 315 | # start all of the tasks 316 | await asyncio.gather(circle_1_task, circle_2_task, button_task) # Don't forget the await! 317 | 318 | 319 | # show main_group so it's visible on the display 320 | display.root_group = main_group 321 | 322 | # start the main coroutine 323 | asyncio.run(main()) 324 | -------------------------------------------------------------------------------- /examples/asyncio_event_example.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 J Fletcher 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # SIMPLE ASYNCIO EVENT EXAMPLE 6 | 7 | # Brief program that illustrates using Events to coordinate tasks 8 | # within Asyncio programs. The present example involves only one led 9 | # and one event, but Asyncio allows a high degree of scaling. Adding 10 | # several copies of the functions 'blink', 'input_poll', or 'state' 11 | # should be straightforward with changes to names and objects. 12 | 13 | import asyncio 14 | 15 | import board 16 | import digitalio 17 | import neopixel 18 | from adafruit_debouncer import Debouncer 19 | 20 | # Import library modules, as is tradition 21 | 22 | pin = digitalio.DigitalInOut(board.BUTTON) 23 | pin.direction = digitalio.Direction.INPUT 24 | pin.pull = digitalio.Pull.UP 25 | button = Debouncer(pin) 26 | 27 | # Instantiate the input, in this case, the 'BOOT' button on a 28 | # QT Py 2040. The debouncer ensures a clean hit. 29 | 30 | BLANK = (0, 0, 0, 0) 31 | RED = (255, 0, 0) 32 | GREEN = (0, 255, 0) 33 | BLUE = (0, 0, 255) 34 | 35 | COLORS = {0: BLANK, 1: RED, 2: GREEN, 3: BLUE} 36 | 37 | # Define the various colors according to preference and set them into 38 | # a dictionary for later retrieval. (Blue is not used in this code.) 39 | 40 | 41 | class Color: 42 | def __init__(self, initial_value): 43 | self.value = initial_value 44 | 45 | 46 | # Create a class to hold and track the color while code executes. 47 | 48 | 49 | async def blink(color): 50 | with neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.1) as led: 51 | while True: 52 | led[0] = COLORS.get(0) 53 | await asyncio.sleep(1) 54 | led[0] = COLORS.get(color.value) 55 | await asyncio.sleep(0) 56 | 57 | 58 | # Instantiate the led using 'with ... as' construction to keep this 59 | # function from blocking. 'COLORS.get(0)' indicates the led should show 60 | # no color (i.e., turn off), while 'COLORS.get(color.value)' instructs 61 | # the led to show the color pulled from the dictionary via the color 62 | # class' color.value. The line 'asyncio.sleep(1)' sets the blink rate; 63 | # in this case, once per second. 64 | 65 | 66 | async def input_poll(swapper): 67 | count = 0 68 | while True: 69 | button.update() 70 | if button.fell: 71 | print("Press!") 72 | if count == 0: 73 | count += 1 74 | print("Event is set!") 75 | swapper.set() 76 | elif count == 1: 77 | count -= 1 78 | print("Event is clear!") 79 | swapper.clear() 80 | await asyncio.sleep(0) 81 | 82 | 83 | # This function checks the button for activity and sets or clears the 84 | # Event depending on the button activity reflected in the 'count' variable. 85 | # The count begins set at 0 and is alternatingly incremented (count += 1) 86 | # and decremented (count -= 1) with each press of the button. 87 | 88 | 89 | async def state(swapper, color): 90 | while True: 91 | if swapper.is_set(): 92 | color.value = 2 93 | else: 94 | color.value = 1 95 | await asyncio.sleep(0) 96 | 97 | 98 | async def main(): 99 | color = Color(1) 100 | COLORS.get(color) 101 | 102 | # Sets the color the led will first show on start 103 | 104 | swapper = asyncio.Event() 105 | 106 | # Creates and names the Event that signals the led to change color 107 | 108 | blinky = asyncio.create_task(blink(color)) 109 | poll = asyncio.create_task(input_poll(swapper)) 110 | monitor = asyncio.create_task(state(swapper, color)) 111 | 112 | # Creates and names Tasks from the functions defined above 113 | 114 | await asyncio.gather(monitor, blinky, poll) 115 | 116 | 117 | # Don't forget the 'await'! The 'asyncio.gather()' command passes the 118 | # listed tasks to the asynchronous scheduler, where processing resources 119 | # are directed from one task to another depending upon whether said task 120 | # has signalled its ability to 'give up' control by reaching the 'await 121 | # asyncio.sleep()' line. 122 | 123 | asyncio.run(main()) 124 | -------------------------------------------------------------------------------- /examples/asyncio_serial_examples.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Phil Underwood 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | """ 5 | example that reads from the cdc data serial port in groups of four and prints 6 | to the console. The USB CDC data serial port will need enabling. This can be done 7 | by copying examples/usb_cdc_boot.py to boot.py in the CIRCUITPY directory 8 | 9 | Meanwhile a simple counter counts up every second and also prints 10 | to the console. 11 | """ 12 | 13 | import asyncio 14 | 15 | USE_USB = True 16 | USE_UART = True 17 | USE_BLE = True 18 | 19 | 20 | if USE_USB: 21 | import usb_cdc 22 | 23 | async def usb_client(): 24 | usb_cdc.data.timeout = 0 25 | s = asyncio.StreamReader(usb_cdc.data) 26 | while True: 27 | text = await s.readline() 28 | print("USB: ", text) 29 | 30 | 31 | if USE_UART: 32 | import board 33 | 34 | async def uart_client(): 35 | uart = board.UART() 36 | uart.timeout = 0 37 | s = asyncio.StreamReader(board.UART()) 38 | while True: 39 | text = await s.readexactly(4) 40 | print("UART: ", text) 41 | 42 | 43 | if USE_BLE: 44 | from adafruit_ble import BLERadio 45 | from adafruit_ble.advertising.standard import ProvideServicesAdvertisement 46 | from adafruit_ble.services.nordic import UARTService 47 | 48 | async def ble_client(): 49 | ble = BLERadio() 50 | uart = UARTService() 51 | advertisement = ProvideServicesAdvertisement(uart) 52 | ble.start_advertising(advertisement) 53 | s = asyncio.StreamReader(uart._rx) 54 | while True: 55 | text = await s.read(6) 56 | print("BLE: ", text) 57 | 58 | 59 | async def counter(): 60 | i = 0 61 | while True: 62 | print(i) 63 | i += 1 64 | await asyncio.sleep(1) 65 | 66 | 67 | async def main(): 68 | clients = [asyncio.create_task(counter())] 69 | if USE_USB: 70 | clients.append(asyncio.create_task(usb_client())) 71 | if USE_UART: 72 | clients.append(asyncio.create_task(uart_client())) 73 | if USE_BLE: 74 | clients.append(asyncio.create_task(ble_client())) 75 | await asyncio.gather(*clients) 76 | 77 | 78 | asyncio.run(main()) 79 | -------------------------------------------------------------------------------- /examples/asyncio_simpletest.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Dan Halbert for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | # -----[Example for one led]----------------------------- 6 | 7 | import asyncio 8 | 9 | import board 10 | import digitalio 11 | 12 | 13 | async def blink(pin, interval, count): # Don't forget the async! 14 | with digitalio.DigitalInOut(pin) as led: 15 | led.switch_to_output(value=False) 16 | for _ in range(count): 17 | led.value = True 18 | await asyncio.sleep(interval) # Don't forget the await! 19 | led.value = False 20 | await asyncio.sleep(interval) # Don't forget the await! 21 | 22 | 23 | async def main(): # Don't forget the async! 24 | led_task = asyncio.create_task(blink(board.D1, 0.25, 10)) 25 | await asyncio.gather(led_task) # Don't forget the await! 26 | print("done") 27 | 28 | 29 | asyncio.run(main()) 30 | 31 | 32 | # -----[Example for two leds]------------------------------- 33 | 34 | # Import modules as above 35 | 36 | 37 | async def blink(pin, interval, count): 38 | with digitalio.DigitalInOut(pin) as led: 39 | led.switch_to_output(value=False) 40 | for _ in range(count): 41 | led.value = True 42 | await asyncio.sleep(interval) # Don't forget the "await"! 43 | led.value = False 44 | await asyncio.sleep(interval) # Don't forget the "await"! 45 | 46 | 47 | async def main(): 48 | led1_task = asyncio.create_task(blink(board.D1, 0.25, 10)) 49 | led2_task = asyncio.create_task(blink(board.D2, 0.1, 20)) 50 | 51 | await asyncio.gather(led1_task, led2_task) # Don't forget "await"! 52 | print("done") 53 | 54 | 55 | asyncio.run(main()) 56 | -------------------------------------------------------------------------------- /examples/asyncio_usb_cdc_boot.py: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Phil Underwood 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | """ 5 | Save this file as boot.py on CIRCUITPY to enable the usb_cdc.data serial device 6 | """ 7 | 8 | import usb_cdc 9 | 10 | usb_cdc.enable(data=True, console=True) 11 | -------------------------------------------------------------------------------- /optional_requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | [build-system] 6 | requires = [ 7 | "setuptools", 8 | "wheel", 9 | "setuptools-scm", 10 | ] 11 | 12 | [project] 13 | name = "adafruit-circuitpython-asyncio" 14 | description = "Cooperative multitasking and asynchronous I/O" 15 | version = "0.0.0+auto.0" 16 | readme = "README.rst" 17 | authors = [ 18 | {name = "Adafruit Industries", email = "circuitpython@adafruit.com"} 19 | ] 20 | urls = {Homepage = "https://github.com/adafruit/Adafruit_CircuitPython_asyncio.git"} 21 | keywords = [ 22 | "adafruit", 23 | "blinka", 24 | "circuitpython", 25 | "micropython", 26 | "asyncio", 27 | "async", 28 | ] 29 | license = {text = "MIT"} 30 | classifiers = [ 31 | "Intended Audience :: Developers", 32 | "Topic :: Software Development :: Libraries", 33 | "Topic :: Software Development :: Embedded Systems", 34 | "Topic :: System :: Hardware", 35 | "License :: OSI Approved :: MIT License", 36 | "Programming Language :: Python :: 3", 37 | ] 38 | dynamic = ["dependencies", "optional-dependencies"] 39 | 40 | [tool.setuptools] 41 | packages = ["asyncio"] 42 | 43 | [tool.setuptools.dynamic] 44 | dependencies = {file = ["requirements.txt"]} 45 | optional-dependencies = {optional = {file = ["optional_requirements.txt"]}} 46 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: Unlicense 4 | 5 | Adafruit-Blinka 6 | adafruit-circuitpython-ticks 7 | -------------------------------------------------------------------------------- /ruff.toml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | target-version = "py38" 6 | line-length = 100 7 | 8 | [lint] 9 | preview = true 10 | select = ["I", "PL", "UP"] 11 | 12 | extend-select = [ 13 | "D419", # empty-docstring 14 | "E501", # line-too-long 15 | "W291", # trailing-whitespace 16 | "PLC0414", # useless-import-alias 17 | "PLC2401", # non-ascii-name 18 | "PLC2801", # unnecessary-dunder-call 19 | "PLC3002", # unnecessary-direct-lambda-call 20 | "E999", # syntax-error 21 | "PLE0101", # return-in-init 22 | "F706", # return-outside-function 23 | "F704", # yield-outside-function 24 | "PLE0116", # continue-in-finally 25 | "PLE0117", # nonlocal-without-binding 26 | "PLE0241", # duplicate-bases 27 | "PLE0302", # unexpected-special-method-signature 28 | "PLE0604", # invalid-all-object 29 | "PLE0605", # invalid-all-format 30 | "PLE0643", # potential-index-error 31 | "PLE0704", # misplaced-bare-raise 32 | "PLE1141", # dict-iter-missing-items 33 | "PLE1142", # await-outside-async 34 | "PLE1205", # logging-too-many-args 35 | "PLE1206", # logging-too-few-args 36 | "PLE1307", # bad-string-format-type 37 | "PLE1310", # bad-str-strip-call 38 | "PLE1507", # invalid-envvar-value 39 | "PLE2502", # bidirectional-unicode 40 | "PLE2510", # invalid-character-backspace 41 | "PLE2512", # invalid-character-sub 42 | "PLE2513", # invalid-character-esc 43 | "PLE2514", # invalid-character-nul 44 | "PLE2515", # invalid-character-zero-width-space 45 | "PLR0124", # comparison-with-itself 46 | "PLR0202", # no-classmethod-decorator 47 | "PLR0203", # no-staticmethod-decorator 48 | "UP004", # useless-object-inheritance 49 | "PLR0206", # property-with-parameters 50 | "PLR0904", # too-many-public-methods 51 | "PLR0911", # too-many-return-statements 52 | "PLR0912", # too-many-branches 53 | "PLR0913", # too-many-arguments 54 | "PLR0914", # too-many-locals 55 | "PLR0915", # too-many-statements 56 | "PLR0916", # too-many-boolean-expressions 57 | "PLR1702", # too-many-nested-blocks 58 | "PLR1704", # redefined-argument-from-local 59 | "PLR1711", # useless-return 60 | "C416", # unnecessary-comprehension 61 | "PLR1733", # unnecessary-dict-index-lookup 62 | "PLR1736", # unnecessary-list-index-lookup 63 | 64 | # ruff reports this rule is unstable 65 | #"PLR6301", # no-self-use 66 | 67 | "PLW0108", # unnecessary-lambda 68 | "PLW0120", # useless-else-on-loop 69 | "PLW0127", # self-assigning-variable 70 | "PLW0129", # assert-on-string-literal 71 | "B033", # duplicate-value 72 | "PLW0131", # named-expr-without-context 73 | "PLW0245", # super-without-brackets 74 | "PLW0406", # import-self 75 | "PLW0602", # global-variable-not-assigned 76 | "PLW0603", # global-statement 77 | "PLW0604", # global-at-module-level 78 | 79 | # fails on the try: import typing used by libraries 80 | #"F401", # unused-import 81 | 82 | "F841", # unused-variable 83 | "E722", # bare-except 84 | "PLW0711", # binary-op-exception 85 | "PLW1501", # bad-open-mode 86 | "PLW1508", # invalid-envvar-default 87 | "PLW1509", # subprocess-popen-preexec-fn 88 | "PLW2101", # useless-with-lock 89 | "PLW3301", # nested-min-max 90 | ] 91 | 92 | ignore = [ 93 | "PLR2004", # magic-value-comparison 94 | "UP030", # format literals 95 | "PLW1514", # unspecified-encoding 96 | "PLR0913", # too-many-arguments 97 | "PLR0915", # too-many-statements 98 | "PLR0917", # too-many-positional-arguments 99 | "PLR0904", # too-many-public-methods 100 | "PLR0912", # too-many-branches 101 | "PLR0916", # too-many-boolean-expressions 102 | "PLR6301", # could-be-static no-self-use 103 | "PLC0415", # import outside toplevel 104 | "E722", # bare except 105 | "E501", # line length 106 | "PLW0603", # global var 107 | "PLW0602", # global var not re-assigned 108 | ] 109 | 110 | [format] 111 | line-ending = "lf" 112 | --------------------------------------------------------------------------------