@@ -1978,34 +1978,55 @@ be blocked, which will delay all other tasks.
19781978
19791979All the built-in functionality in pyscript is written using asynchronous code, which runs seamlessly
19801980together with all the other tasks in the main event loop. However, if you import Python packages and
1981- call functions that block (e.g., file or network I/O) then you need to run those functions outside
1982- the main event loop. That can be accomplished by wrapping those function calls with the
1983- ``task.executor `` function, which runs the function in a separate thread:
1981+ call functions that block (any type of file, network I/O, http etc), you will block the main loop and
1982+ that will make HASS less responsive. HASS might report an error like this:
1983+
1984+ ::
1985+
1986+ WARNING (MainThread) [homeassistant.util.loop] Detected blocking call to ... inside the event loop by custom
1987+ integration 'pyscript' at custom_components/pyscript/eval.py, line 1982: return func(*args, **kwargs)
1988+ (offender: ...), please create a bug report at https://github.com/custom-components/pyscript/issues
1989+
1990+ For developers, please see https://developers.home-assistant.io/docs/asyncio_blocking_operations/#open
1991+
1992+ This warning is a reminder that you should not block the main loop. Do not file a bug report - the
1993+ issue is almost certainly in your code, not pyscript. You should review
1994+ the `developer link <https://developers.home-assistant.io/docs/asyncio_blocking_operations/#open >`__ for
1995+ a good summary of the numerous ways you can inadvently write pyscript code that blocks the main event loop.
1996+
1997+ Currently built-in functions that do I/O, such as ``open ``, ``read `` and ``write `` are not supported
1998+ in pyscript to avoid I/O in the main event loop, and also to avoid security issues if people share pyscripts.
1999+ Also, the ``print `` function only logs a message, rather than implements the real ``print `` features, such
2000+ as specifying an output file handle.
2001+
2002+ The ``task.executor `` function is a way to run a blocking function in a separate thread, so it doesn't
2003+ stall the main event loop. It's a good way to run blocking code, but it's not as efficient as using
2004+ async I/O directly:
19842005
19852006``task.executor(func, *args, **kwargs) ``
19862007 Run the given function in a separate thread. The first argument is the function to be called,
19872008 followed by each of the positional or keyword arguments that function expects. The ``func ``
19882009 argument can only be a regular Python function, not a function defined in pyscript.
19892010
1990- If you forget to use ``task.executor ``, you might get this warning from HASS:
1991-
1992- ::
1993-
1994- WARNING (MainThread) [homeassistant.util.async_] Detected I/O inside the event loop. This is
1995- causing stability issues. Please report issue to the custom component author for pyscript doing
1996- I/O at custom_components/pyscript/eval.py, line 1583: return func(*args, **kwargs)
1997-
1998- Currently the built-in functions that do I/O, such as ``open ``, ``read `` and ``write `` are not supported
1999- to avoid I/O in the main event loop, and also to avoid security issues if people share pyscripts. Also,
2000- the ``print `` function only logs a message, rather than implements the real ``print `` features, such
2001- as specifying an output file handle. If you want to do file I/O from pyscript, you have two choices:
2011+ If you want to do file or network I/O from pyscript, or make any system calls that might block,
2012+ three are three main choices:
20022013
2014+ - Use async versions of the I/O functions you need (eg, ``ascyncio ``, ``aiohttp `` etc). This is the
2015+ recommended approach.
20032016- put the code in a separate native Python module, so that functions like ``open ``, ``read `` and ``write ``
20042017 are available, and call the function in that module from pyscript using ``task.executor ``. See
20052018 `Importing <#importing >`__ for how to set Python's ``sys.path `` to import a local Python module.
2006- - you could use the ``os `` package (which can be imported by setting ``allow_all_imports ``) and
2007- calling the low-level functions like ``os.open `` and ``os.read `` using ``task.executor `` to
2008- wrap every function.
2019+ - if you really need to do file I/O directly, you could use the ``os `` package (which can be imported by
2020+ setting ``allow_all_imports ``) and calling the low-level functions like ``os.open `` and ``os.read `` using
2021+ ``task.executor `` to wrap every function that calls those blocking functions.
2022+
2023+ An additional trap is using modules that do lazy loading (e.g., `pytz `), which load certain data only when
2024+ needed (e.g., specific time zone data in the case of `pytz `). That delays the blocking file I/O until
2025+ run-time (when it's running in the main event loop), which is bad, rather than at load time when pyscript
2026+ loads it in a separate thread. So you will need to avoid lazy loading modules, or be sure to call them
2027+ at load time (i.e., outside a function in one of your scripts) in a manner that causes them to load all
2028+ the data they need. For `pytz ` , you should use `zoneinfo ` instead, which is in the standard library and
2029+ doesn't appear to do lazy loading.
20092030
20102031Here's an example fetching a URL. Inside pyscript, this is the wrong way since it does I/O without
20112032using a separate thread:
@@ -2014,6 +2035,7 @@ using a separate thread:
20142035
20152036 import requests
20162037
2038+ # Do not fetch URLs this way!
20172039 url = " https://raw.githubusercontent.com/custom-components/pyscript/master/README.md"
20182040 resp = requests.get(url)
20192041
@@ -2023,6 +2045,7 @@ The correct way is:
20232045
20242046 import requests
20252047
2048+ # Better - uses task.executor to run the blocking function in a separate thread
20262049 url = " https://raw.githubusercontent.com/custom-components/pyscript/master/README.md"
20272050 resp = task.executor(requests.get, url)
20282051
@@ -2034,6 +2057,7 @@ is optional in pyscript):
20342057
20352058 import aiohttp
20362059
2060+ # Best - uses async I/O to avoid blocking the main event loop
20372061 url = " https://raw.githubusercontent.com/custom-components/pyscript/master/README.md"
20382062 async with aiohttp.ClientSession() as session:
20392063 async with session.get(url) as resp:
@@ -2099,10 +2123,8 @@ Language Limitations
20992123Pyscript implements a Python interpreter in a fully-async manner, which means it can run safely in the
21002124main HASS event loop.
21012125
2102- The language coverage is relatively complete, but it's quite possible there are discrepancies with Python
2103- in certain cases. If so, please report them.
2104-
2105- Here are some areas where pyscript differs from real Python:
2126+ The language coverage is relatively complete, but there are definitely limitations in pyscript where
2127+ it doesn't faithfully mimic Python. Here are some areas where pyscript differs from real Python:
21062128
21072129- The pyscript-specific function names and state names that contain a period are treated as plain
21082130 identifiers that contain a period, rather than an attribute (to the right of the period) of an object
@@ -2114,6 +2136,9 @@ Here are some areas where pyscript differs from real Python:
21142136 function is no longer available.
21152137- Since pyscript is async, it detects whether functions are real or async, and calls them in the
21162138 correct manner. So it's not necessary to use ``async `` and ``await `` in pyscript code - they are optional.
2139+ However, if you declare a function in pyscript as ``async def ``, then it doesn't behave correctly
2140+ like an async function in Python (i.e., calling it actually executes the function, rather than returning
2141+ a co-routine. If you truly need an async function in your code, use `@pyscript_compile `.
21172142- All pyscript functions are async. So if you call a Python module that takes a pyscript function as
21182143 a callback argument, that argument is an async function, not a normal function. So a Python module
21192144 won't be able to call that pyscript function unless it uses ``await ``, which requires that function to
@@ -2145,11 +2170,13 @@ A handful of language features are not supported:
21452170 function only logs a message, rather than implements the real ``print `` features, such as specifying
21462171 an output file handle.
21472172- The built-in function decorators (e.g., ``state_trigger ``) aren't functions that can be called and used
2148- in-line. However, you can define your own function decorators that could include those decorators on
2149- the inner functions they define. Currently none of Python's built-in decorators are supported.
2150-
2151- Pyscript can call Python modules and packages, so you can always write your own native Python code
2152- (e.g., if you need a generator or other unsupported feature) that can be called by pyscript
2153- (see `Importing <#importing >`__ for how to create and import native Python modules in pyscript).
2173+ in-line like real Python decorators. However, you can define your own function decorators that
2174+ could include those decorators on the inner functions they define. Currently none of Python's
2175+ built-in decorators are supported.
2176+
2177+ The typical work-around for places where pyscript falls short is to move that code into a native Python module,
2178+ and then import that module into pyscript. Pyscript can call Python modules and packages, so you could
2179+ write your own native Python code (e.g., if you need a generator or other unsupported feature) that can be
2180+ called by pyscript (see `Importing <#importing >`__ for how to create and import native Python modules in pyscript).
21542181You can also include native Python functions in your pyscript code by using the ``@pyscript_compile ``
21552182decorator.
0 commit comments