diff --git a/homeassistant/__init__.py b/homeassistant/__init__.py index 1da1060af55e..525aa3f8ef24 100644 --- a/homeassistant/__init__.py +++ b/homeassistant/__init__.py @@ -1,5 +1,6 @@ """Init file for Home Assistant.""" +import probatio from probatio import BuildPolicy, set_build_policy from probatio.compat import install_as_voluptuous @@ -13,3 +14,11 @@ install_as_voluptuous() # a large number of schemas, many of which are never validated in a given run, so # lazy building avoids that upfront cost. Only the application may set this policy. set_build_policy(BuildPolicy.LAZY) + +# Probatio resolves its codec re-exports through a lazy import on first attribute +# access. Both of these are reached from the event loop, where that import is a +# blocking call: to_field_list renders every config flow form, to_openapi builds +# the tool schemas for a conversation turn. Resolve them here instead. It costs +# about 4 ms and pulls in no voluptuous of its own. +_ = probatio.to_field_list +_ = probatio.to_openapi diff --git a/tests/test_init.py b/tests/test_init.py new file mode 100644 index 000000000000..a197ec8378dd --- /dev/null +++ b/tests/test_init.py @@ -0,0 +1,32 @@ +"""Test the Home Assistant package init.""" + +import subprocess +import sys + +IMPORT_TIMEOUT = 60 + + +def test_probatio_codecs_is_imported_before_the_event_loop() -> None: + """Importing Home Assistant must leave no lazy codec import for the loop. + + Probatio resolves to_field_list and to_openapi through a lazy import of + probatio.codecs on first attribute access, and both are reached from the + event loop: the first renders every config flow form, the second builds the + tool schemas for a conversation turn. + + A clean interpreter is the only way to see this: anything the test suite + imported first would hide it. + """ + result = subprocess.run( + [ + sys.executable, + "-c", + "import sys, homeassistant; print('probatio.codecs' in sys.modules)", + ], + capture_output=True, + check=True, + text=True, + timeout=IMPORT_TIMEOUT, + ) + + assert result.stdout.strip() == "True"