Skip to main content

A highly opinionated flake8 plugin for Trio-related problems.

Project description

pre-commit.ci status Checked with pyright

flake8-async

A highly opinionated flake8 plugin for problems related to Trio, AnyIO, or asyncio.

This can include anything from outright bugs, to pointless/dead code, to likely performance issues, to minor points of idiom that might signal a misunderstanding.

It may well be too noisy for anyone with different opinions, that's OK.

Pairs well with flake8-bugbear.

Some checks are incorporated into ruff.

This plugin was previously known as flake8-trio, and there was a separate small plugin known as flake8-async for asyncio. But this plugin was a superset of the checks in flake8-async, and support for anyio was added, so it's now named flake8-async to more properly convey its usage. At the same time all error codes were renamed from TRIOxxx to ASYNCxxx, as was previously used by the old flake8-async.

Installation

pip install flake8-async

List of warnings

  • ASYNC100: A with [trio/anyio].fail_after(...): or with [trio/anyio].move_on_after(...): context does not contain any await statements. This makes it pointless, as the timeout can only be triggered by a checkpoint. This check also allows yield statements, since checkpoints can happen in the caller we yield to.
  • ASYNC101: yield inside a trio/anyio nursery or cancel scope is only safe when implementing a context manager - otherwise, it breaks exception handling.
  • ASYNC102: It's unsafe to await inside finally: or except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError unless you use a shielded cancel scope with a timeout. This is currently not able to detect asyncio shields.
  • ASYNC103: except BaseException/trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError, or a bare except: with a code path that doesn't re-raise. If you don't want to re-raise BaseException, add a separate handler for trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError before.
  • ASYNC104: trio.Cancelled/anyio.get_cancelled_exc_class()/asyncio.exceptions.CancelledError/BaseException must be re-raised. The same as ASYNC103, except specifically triggered on return or a different exception being raised.
  • ASYNC105: Calling a trio async function without immediately awaiting it. This is only supported with trio functions, but you can get similar functionality with a type-checker.
  • ASYNC106: trio/anyio/asyncio must be imported with import trio/import anyio/import asyncio for the linter to work.
  • ASYNC109: Async function definition with a timeout parameter - use [trio/anyio].[fail/move_on]_[after/at] instead.
  • ASYNC110: while <condition>: await [trio/anyio].sleep() should be replaced by a [trio/anyio].Event.
  • ASYNC111: Variable, from context manager opened inside nursery, passed to start[_soon] might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
  • ASYNC112: Nursery body with only a call to nursery.start[_soon] and not passing itself as a parameter can be replaced with a regular function call.
  • ASYNC113: Using nursery.start_soon in __aenter__ doesn't wait for the task to begin. Consider replacing with nursery.start.
  • ASYNC114: Startable function (i.e. has a task_status keyword parameter) not in --startable-in-context-manager parameter list, please add it so ASYNC113 can catch errors when using it.
  • ASYNC115: Replace [trio/anyio].sleep(0) with the more suggestive [trio/anyio].lowlevel.checkpoint().
  • ASYNC116: [trio/anyio].sleep() with >24 hour interval should usually be [trio/anyio].sleep_forever().
  • ASYNC118: Don't assign the value of anyio.get_cancelled_exc_class() to a variable, since that breaks linter checks and multi-backend programs.
  • ASYNC119: yield in context manager in async generator is unsafe, the cleanup may be delayed until await is no longer allowed. We strongly encourage you to read PEP-533 and use async with aclosing(...), or better yet avoid async generators entirely (see ASYNC900) in favor of context managers which return an iterable channel/queue.

Warnings for blocking sync calls in async functions

Note: 22X, 23X and 24X has not had asyncio-specific suggestions written.

  • ASYNC200: User-configured error for blocking sync calls in async functions. Does nothing by default, see async200-blocking-calls for how to configure it.
  • ASYNC210: Sync HTTP call in async function, use httpx.AsyncClient. This and the other ASYNC21x checks look for usage of urllib3 and httpx.Client, and recommend using httpx.AsyncClient as that's the largest http client supporting anyio/trio.
  • ASYNC211: Likely sync HTTP call in async function, use httpx.AsyncClient. Looks for urllib3 method calls on pool objects, but only matching on the method signature and not the object.
  • ASYNC212: Blocking sync HTTP call on httpx object, use httpx.AsyncClient.
  • ASYNC220: Sync process call in async function, use await nursery.start([trio/anyio].run_process, ...). asyncio users can use asyncio.create_subprocess_[exec/shell].
  • ASYNC221: Sync process call in async function, use await [trio/anyio].run_process(...). asyncio users can use asyncio.create_subprocess_[exec/shell].
  • ASYNC222: Sync os.* call in async function, wrap in await [trio/anyio].to_thread.run_sync(). asyncio users can use asyncio.loop.run_in_executor.
  • ASYNC230: Sync IO call in async function, use [trio/anyio].open_file(...). asyncio users need to use a library such as aiofiles, or switch to anyio.
  • ASYNC231: Sync IO call in async function, use [trio/anyio].wrap_file(...). asyncio users need to use a library such as aiofiles, or switch to anyio.
  • ASYNC232: Blocking sync call on file object, wrap the file object in [trio/anyio].wrap_file() to get an async file object.
  • ASYNC240: Avoid using os.path in async functions, prefer using [trio/anyio].Path objects. asyncio users should consider aiopath or anyio.
  • ASYNC250: Builtin input() should not be called from async function. Wrap in [trio/anyio].to_thread.run_sync() or asyncio.loop.run_in_executor().
  • ASYNC251: time.sleep(...) should not be called from async function. Use [trio/anyio/asyncio].sleep(...).

Warnings disabled by default

  • ASYNC900: Async generator without @asynccontextmanager not allowed. You might want to enable this on a codebase since async generators are inherently unsafe and cleanup logic might not be performed. See https://github.com/python-trio/flake8-async/issues/211 and https://discuss.python.org/t/using-exceptiongroup-at-anthropic-experience-report/20888/6 for discussion.
  • ASYNC910: Exit or return from async function with no guaranteed checkpoint or exception since function definition. You might want to enable this on a codebase to make it easier to reason about checkpoints, and make the logic of ASYNC911 correct.
  • ASYNC911: Exit, yield or return from async iterable with no guaranteed checkpoint since possible function entry (yield or function definition) Checkpoints are await, async for, and async with (on one of enter/exit).

Removed Warnings

  • TRIOxxx: All error codes are now renamed ASYNCxxx
  • TRIO107: Renamed to TRIO910
  • TRIO108: Renamed to TRIO911
  • TRIO117: Don't raise or catch trio.[NonBase]MultiError, prefer [exceptiongroup.]BaseExceptionGroup. MultiError was removed in trio==0.24.0.

Examples

install and run through flake8

pip install flake8 flake8-async
flake8 .

install and run with pre-commit

If you use pre-commit, you can use it with flake8-async by adding the following to your .pre-commit-config.yaml:

minimum_pre_commit_version: '2.9.0'
repos:
- repo: https://github.com/python-trio/flake8-async
  rev: 24.4.2
  hooks:
    - id: flake8-async
      # args: [--enable=ASYNC, --disable=ASYNC9, --autofix=ASYNC]

This is often considerably faster for large projects, because pre-commit can avoid running flake8-async on unchanged files.

Afterwards, run

pip install pre-commit flake8-async
pre-commit run .

install and run as standalone

If inside a git repository, running without arguments will run it against all *.py files in the repository.

pip install flake8-async
flake8-async

with autofixes

flake8-async --autofix=ASYNC

specifying source files

flake8-async my_python_file.py
zsh-only
flake8-async **/*.py

Configuration

You can configure flake8 with command-line options, but we prefer using a config file. The file needs to start with a section marker [flake8] and the following options are then parsed using flake8's config parser, and can be used just like any other flake8 options. Note that it's not currently possible to use a configuration file when running flake8-async standalone.

ValueError when trying to ignore error codes in config file

Error codes with more than three letters are not possible to ignore in config files since flake8>=6, as flake8 tries to validate correct configuration with a regex. We have decided not to conform to this, as it would be a breaking change for end-users requiring them to update noqas and configurations, we think the ASYNC code is much more readable than e.g. ASYxxx, and ruff does not enforce such a limit. The easiest option for users hitting this error is to instead use the --disable option as documented below. See further discussion and other workarounds in https://github.com/python-trio/flake8-async/issues/230

--enable

Comma-separated list of error codes to enable, similar to flake8 --select but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.

--disable

Comma-separated list of error codes to disable, similar to flake8 --ignore but is additionally more performant as it will disable non-enabled visitors from running instead of just silencing their errors.

--autofix

Comma-separated list of error-codes to enable autofixing for if implemented. Requires running as a standalone program. Pass --autofix=ASYNC to enable all autofixes.

--error-on-autofix

Whether to also print an error message for autofixed errors.

--anyio

Change the default library to be anyio instead of trio. If trio is imported it will assume both are available and print suggestions with [anyio/trio].

no-checkpoint-warning-decorators

Comma-separated list of decorators to disable checkpointing checks for, turning off ASYNC910 and ASYNC911 warnings for functions decorated with any decorator matching any in the list. Matching is done with fnmatch. Defaults to disabling for asynccontextmanager.

Decorators-to-match must be identifiers or dotted names only (not PEP-614 expressions), and will match against the name only - e.g. foo.bar matches foo.bar, foo.bar(), and foo.bar(args, here), etc.

For example:

no-checkpoint-warning-decorators =
  mydecorator,
  mydecoratorpackage.checkpointing_decorators.*,
  ign*,
  *.ignore,

startable-in-context-manager

Comma-separated list of methods which should be used with .start() when opening a context manager, in addition to the default trio.run_process, trio.serve_tcp, trio.serve_ssl_over_tcp, and trio.serve_listeners. Names must be valid identifiers as per str.isidentifier(). For example:

startable-in-context-manager =
  myfun,
  myfun2,

async200-blocking-calls

Comma-separated list of pairs of values separated by -> (optional whitespace stripped), where the first is a pattern for a call that should raise an error if found inside an async function, and the second is what should be suggested to use instead. It uses fnmatch as per no-checkpoint-warning-decorators for matching. The part after -> is not used by the checker other than when printing the error, so you could add extra info there if you want.

The format of the error message is User-configured blocking sync call {0} in async function, consider replacing with {1}., where {0} is the pattern the call matches and {1} is the suggested replacement.

Example:

async200-blocking-calls =
  my_blocking_call -> async.alternative,
  module.block_call -> other_function_to_use,
  common_error_call -> alternative(). But sometimes you should use other_function(). Ask joe if you're unsure which one,
  dangerous_module.* -> corresponding function in safe_module,
  *.dangerous_call -> .safe_call()

Specified patterns must not have parentheses, and will only match when the pattern is the name of a call, so given the above configuration

async def my_function():
    my_blocking_call()  # this would raise an error
    x = my_blocking_call(a, b, c)  # as would this
    y = my_blocking_call  # but not this
    y()  # or this
    [my_blocking_call][0]()  # nor this

    def my_blocking_call():  # it's also safe to use the name in other contexts
        ...

    arbitrary_other_function(my_blocking_call=None)

Changelog

CalVer, YY.month.patch

24.4.2

  • Add ASYNC119: yield in contextmanager in async generator.

24.4.1

  • ASYNC91X fix internal error caused by multiple try/except incorrectly sharing state.

24.3.6

  • ASYNC100 no longer triggers if a context manager contains a yield.

24.3.5

24.3.4

  • ASYNC110 (don't loop sleep) now also warns if looping [trio/anyio].lowlevel.checkpoint().

24.3.3

  • Add ASYNC251: time.sleep() in async method.

24.3.2

  • Add ASYNC250: blocking sync call input() in async method.

24.3.1

  • Removed TRIO117, MultiError removed in trio 0.24.0
  • Renamed the library from flake8-trio to flake8-async, to indicate the checker supports more than just trio.
  • Renamed all error codes from TRIOxxx to ASYNCxxx
  • Renamed the binary from flake8-trio to flake8-async
  • Lots of internal renaming.
  • Added asyncio support for several error codes
  • added --library

23.5.1

  • TRIO91X now supports comprehensions
  • TRIO100 and TRIO91X now supports autofixing
  • Renamed --enable-visitor-codes-regex to --enable
  • Added --disable, --autofix and --error-on-autofix

23.2.5

  • Fix false alarms for @pytest.fixture-decorated functions in TRIO101, TRIO910 and TRIO911

23.2.4

  • Fix TRIO900 false alarm on nested functions
  • TRIO113 now also works on anyio.TaskGroup

23.2.3

  • Fix get_matching_call when passed a single string as base. Resolves possibly several false alarms, TRIO210 among them.

23.2.2

  • Rename TRIO107 to TRIO910, and TRIO108 to TRIO911, and making them optional by default.
  • Allow @pytest.fixture()-decorated async generators, since they're morally context managers
  • Add support for checking code written against anyio
  • Add TRIO118: Don't assign the value of anyio.get_cancelled_exc_class() to a variable, since that breaks linter checks and multi-backend programs.

23.2.1

  • TRIO103 and TRIO104 no longer triggers when trio.Cancelled has been handled in previous except handlers.
  • Add TRIO117: Reference to deprecated trio.[NonBase]MultiError; use [Base]ExceptionGroup instead.
  • Add TRIO232: blocking sync call on file object.
  • Add TRIO212: blocking sync call on httpx.Client object.
  • Add TRIO222: blocking sync call to os.wait*
  • TRIO221 now also looks for os.posix_spawn[p]

23.1.4

  • TRIO114 avoids a false alarm on posonly args named "task_status"
  • TRIO116 will now match on any attribute parameter named .inf, not just math.inf.
  • TRIO900 now only checks @asynccontextmanager, not other decorators passed with --no-checkpoint-warning-decorators.

23.1.3

  • Add TRIO240: usage of os.path in async function.
  • Add TRIO900: ban async generators not decorated with known safe decorator

23.1.2

  • Add TRIO230, TRIO231 - sync IO calls in async function

23.1.1

  • Add TRIO210, TRIO211 - blocking sync call in async function, using network packages (requests, httpx, urllib3)
  • Add TRIO220, TRIO221 - blocking sync call in async function, using subprocess or os.

22.12.5

  • The --startable-in-context-manager and --trio200-blocking-calls options now handle spaces and newlines.
  • Now compatible with flake8-noqa's NQA102 and NQA103 checks.

22.12.4

  • TRIO200 no longer warns on directly awaited calls

22.12.3

  • Worked around configuration-parsing bug for TRIO200 warning (more to come)

22.12.2

  • Add TRIO200: User-configured blocking sync call in async function

22.12.1

  • TRIO114 will now trigger on the unqualified name, will now only check the first parameter directly, and parameters to function calls inside that.
  • TRIO113 now only supports names that are valid identifiers, rather than fnmatch patterns.
  • Add TRIO115: Use trio.lowlevel.checkpoint() instead of trio.sleep(0).

22.11.5

  • Add TRIO116: trio.sleep() with >24 hour interval should usually be trio.sleep_forever().

22.11.4

  • Add TRIO114 Startable function not in --startable-in-context-manager parameter list.

22.11.3

  • Add TRIO113, prefer await nursery.start(...) to nursery.start_soon() for compatible functions when opening a context manager

22.11.2

  • TRIO105 now also checks that you awaited nursery.start().

22.11.1

  • TRIO102 is no longer skipped in (async) context managers, since it's not a missing-checkpoint warning.

22.9.2

  • Fix a crash on nontrivial decorator expressions (calls, PEP-614) and document behavior.

22.9.1

  • Add --no-checkpoint-warning-decorators option, to disable missing-checkpoint warnings for certain decorated functions.

22.8.8

  • Fix false alarm on TRIO107 with checkpointing try and empty finally
  • Fix false alarm on TRIO107&108 with infinite loops

22.8.7

  • TRIO107+108 now ignores asynccontextmanagers, since both __aenter__ and __aexit__ should checkpoint. async with is also treated as checkpointing on both enter and exit.
  • TRIO107 now completely ignores any function whose body consists solely of ellipsis, pass, or string constants.
  • TRIO103, 107 and 108 now inspects while conditions and for iterables to avoid false alarms on a couple cases where the loop body is guaranteed to run at least once.

22.8.6

  • TRIO103 now correctly handles raises in loops, i.e. raise in else is guaranteed to run unless there's a break in the body.

22.8.5

  • Add TRIO111: Variable, from context manager opened inside nursery, passed to start[_soon] might be invalidly accessed while in use, due to context manager closing before the nursery. This is usually a bug, and nurseries should generally be the inner-most context manager.
  • Add TRIO112: this single-task nursery could be replaced by awaiting the function call directly.

22.8.4

  • Fix TRIO108 raising errors on yields in some sync code.
  • TRIO109 now skips all decorated functions to avoid false alarms

22.8.3

  • TRIO108 now gives multiple error messages; one for each path lacking a guaranteed checkpoint

22.8.2

  • Merged TRIO108 into TRIO107
  • TRIO108 now handles checkpointing in async iterators

22.8.1

  • Added TRIO109: Async definitions should not have a timeout parameter. Use trio.[fail/move_on]_[at/after]
  • Added TRIO110: while <condition>: await trio.sleep() should be replaced by a trio.Event.

22.7.6

  • Extend TRIO102 to also check inside except BaseException and except trio.Cancelled
  • Extend TRIO104 to also check for yield
  • Update error messages on TRIO102 and TRIO103

22.7.5

  • Add TRIO103: except BaseException or except trio.Cancelled with a code path that doesn't re-raise
  • Add TRIO104: "Cancelled and BaseException must be re-raised" if user tries to return or raise a different exception.
  • Added TRIO107: Async functions must have at least one checkpoint on every code path, unless an exception is raised
  • Added TRIO108: Early return from async function must have at least one checkpoint on every code path before it.

22.7.4

  • Added TRIO105 check for not immediately awaiting async trio functions.
  • Added TRIO106 check that trio is imported in a form that the plugin can easily parse.

22.7.3

  • Added TRIO102 check for unsafe checkpoints inside finally: blocks

22.7.2

  • Avoid TRIO100 false-alarms on cancel scopes containing async for or async with.

22.7.1

  • Initial release with TRIO100 and TRIO101

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

flake8_async-24.4.2.tar.gz (105.2 kB view details)

Uploaded Source

Built Distribution

flake8_async-24.4.2-py3-none-any.whl (52.7 kB view details)

Uploaded Python 3

File details

Details for the file flake8_async-24.4.2.tar.gz.

File metadata

  • Download URL: flake8_async-24.4.2.tar.gz
  • Upload date:
  • Size: 105.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.10.12

File hashes

Hashes for flake8_async-24.4.2.tar.gz
Algorithm Hash digest
SHA256 6b9d14498301b3f6a16d6d406d9096ff712e634b08cfe3b8dcd5fdf18924855a
MD5 144af046d00a1f25c9a1b74f2972c6fb
BLAKE2b-256 a09fef4fed29e1d3eb989935e4b5b39eb2a94423c2c36c90fefdf0ab530c3768

See more details on using hashes here.

Provenance

File details

Details for the file flake8_async-24.4.2-py3-none-any.whl.

File metadata

File hashes

Hashes for flake8_async-24.4.2-py3-none-any.whl
Algorithm Hash digest
SHA256 e3fe32cc27e140d065e88e11074ad079b0ca1d6a1262afddb8ed7503fe4442a9
MD5 352b5f4af6dbed749f3d9e063fb77427
BLAKE2b-256 d47fd65151206d920a859c81257826a2b813467befdf7ec73ee7189a1e088d92

See more details on using hashes here.

Provenance

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page