Add additional docs for importing code with asyncio (#2338)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
J. Nick Koston 2025-04-16 10:52:32 -10:00 committed by GitHub
parent 7c85921b92
commit 87162ada68
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 62 additions and 9 deletions

View File

@ -53,15 +53,7 @@ When an `open` call running in the event loop is fixed, all the blocking reads a
#### import_module
When importing a module, the import machinery has to read the module from disk which does blocking I/O. Importing modules is both CPU-intensive and involves blocking I/O, so it is crucial to ensure these operations are executed in the executor.
Importing code in [cpython is not thread-safe](https://github.com/python/cpython/issues/83065). If the module will only ever be imported in a single place, the standard executor calls can be used. If there's a possibility of the same module being imported concurrently in different parts of the application, use the thread-safe `homeassistant.helpers.importlib.import_module` helper.
Example:
```python
platform = await async_import_module(hass, f"homeassistant.components.homeassistant.triggers.{platform_name}")
```
See [Importing code with asyncio](asyncio_imports.md)
#### sleep

60
docs/asyncio_imports.md Normal file
View File

@ -0,0 +1,60 @@
---
title: "Importing code with asyncio"
---
Determining when it is safe to import code when using asyncio can be tricky because two constraints need to be considered:
- Importing code can do blocking I/O to load the files from the disk
- Importing code in [cpython is not thread-safe](https://github.com/python/cpython/issues/83065)
## Module level imports
If your imports are at the **module level** (also called **top-level imports**) and all the necessary modules are imported in `__init__.py`, Home Assistant will load your integration either **before the event loop starts** or in a background thread using the **import executor**.
In this scenario, your imports are generally handled safely, so you **dont need to worry** about whether theyre event-loop safe.
## Imports outside of module level
If your imports are not happening at module level, you must carefully consider each import, as the import machinery has to read the module from disk which does blocking I/O. If possible, it's usually best to change to a module level import, as it avoids much complexity and the risk of mistakes. Importing modules is both CPU-intensive and involves blocking I/O, so it is crucial to ensure these operations are executed in the executor.
If you can be sure that the modules have already been imported, using a bare [`import`](https://docs.python.org/3/reference/simple_stmts.html#import) statement is safe since Python will not load the modules again.
If the integration will always use the module, it's usually best to include a module-level import in `__init__.py` to ensure the module is loaded. However, if this creates a circular import, one of the solutions below will need to be used instead.
If the module is only used conditionally, and will only ever be imported in a single place, the standard executor calls can be used:
- For imports inside of Home Assistant `hass.async_add_executor_job(_function_that_does_late_import)`
- For imports outside of Home Assistant: [`loop.run_in_executor(None, _function_that_does_late_import)`](https://docs.python.org/3/library/asyncio-eventloop.html#asyncio.loop.run_in_executor)
If the same module may be imported concurrently in different parts of the application, use the thread-safe `homeassistant.helpers.importlib.import_module` helper.
If it's possible the module may be imported from multiple different paths, use `async_import_module`:
Example:
```python
from homeassistant.helpers.importlib import async_import_module
platform = await async_import_module(hass, f"homeassistant.components.homeassistant.triggers.{platform_name}")
```
## Determining if a module is already loaded
If you are unsure if a module is already loaded, you can check if the module is already in [`sys.modules`](https://docs.python.org/3/library/sys.html#sys.modules). You should know that the module will appear in `sys.modules` as soon as it begins loading, and [cpython imports are not thread-safe](https://github.com/python/cpython/issues/83065). For this reason, it's important to consider race conditions when code may be imported from multiple paths.
## Avoiding imports that are only used for type-checking
If an imported module is only used for type checking, it is recommended to guard it with an `if TYPE_CHECKING:` block to avoid it being imported at runtime.
```python
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from some_module import SomeClass # Only imported for type checking
def some_function() -> SomeClass:
# Function implementation
pass
```
## Avoid importing code that is rarely used
Importing modules can be both CPU and I/O intensive, so its important to avoid importing code that will rarely be used. While importing code outside the module level does add some runtime overhead, this approach is often more efficient when the code is only needed occasionally. By deferring imports, you ensure that resources are only used when necessary, reducing unnecessary processing and improving overall performance.

View File

@ -318,6 +318,7 @@ module.exports = {
"asyncio_working_with_async",
"asyncio_thread_safety",
"asyncio_blocking_operations",
"asyncio_imports",
],
},
],