diff --git a/docs/core/integration-quality-scale/rules/action-setup.md b/docs/core/integration-quality-scale/rules/action-setup.md
index a532d3b7033..f7664c721ab 100644
--- a/docs/core/integration-quality-scale/rules/action-setup.md
+++ b/docs/core/integration-quality-scale/rules/action-setup.md
@@ -24,24 +24,36 @@ This is used to first fetch the configuration entry, and then check if it is loa
If the configuration entry does not exist or the configuration entry that we found is not loaded, we raise a relevant error which is shown to the user.
Supply description placeholders to enable translation of service parameters, for example, to reference external resources like documentation URLs that need to be localized or updated independently of the service description.
-`__init__py`:
-```python {13-20} showLineNumbers
+`__init__.py`:
+```python {6} showLineNumbers
+from .services import async_setup_services
+
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up my integration."""
- async def async_get_schedule(call: ServiceCall) -> ServiceResponse:
- """Get the schedule for a specific range."""
- if not (entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY_ID])):
- raise ServiceValidationError("Entry not found")
- if entry.state is not ConfigEntryState.LOADED:
- raise ServiceValidationError("Entry not loaded")
- client = cast(MyConfigEntry, entry).runtime_data
- ...
+ async_setup_services(hass)
+ return True
+```
+
+`services.py`:
+```python {14-21} showLineNumbers
+async def _async_get_schedule(call: ServiceCall) -> ServiceResponse:
+ """Get the schedule for a specific range."""
+ if not (entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY_ID])):
+ raise ServiceValidationError("Entry not found")
+ if entry.state is not ConfigEntryState.LOADED:
+ raise ServiceValidationError("Entry not loaded")
+ client = cast(MyConfigEntry, entry).runtime_data
+ ...
+
+@callback
+def async_setup_services(hass: HomeAssistant) -> None:
+ """Register the services."""
hass.services.async_register(
DOMAIN,
SERVICE_GET_SCHEDULE,
- async_get_schedule,
+ _async_get_schedule,
schema=SERVICE_GET_SCHEDULE_SCHEMA,
supports_response=SupportsResponse.ONLY,
description_placeholders={"example_url": "https://schedule.example.com"}
@@ -58,4 +70,4 @@ There are no exceptions to this rule.
## Related rules
-
\ No newline at end of file
+
diff --git a/docs/core/integration-quality-scale/rules/common-modules.md b/docs/core/integration-quality-scale/rules/common-modules.md
index e03a90d0042..96e4021291d 100644
--- a/docs/core/integration-quality-scale/rules/common-modules.md
+++ b/docs/core/integration-quality-scale/rules/common-modules.md
@@ -13,6 +13,10 @@ The second common pattern is the base entity.
Since a lot of integrations provide more types of entities, a base entity can prove useful to reduce code duplication.
The base entity should be placed in `entity.py`.
+The third common pattern relates to action definition and registration.
+Placing these in a separate module helps improve code clarity and separation of concern.
+The action definition and registration should be placed in `services.py`.
+
The efforts done to increase consistency between integrations have a positive impact on the quality of the codebase and the developer experience.
## Example implementation
@@ -48,6 +52,32 @@ class MyEntity(CoordinatorEntity[MyCoordinator]):
self._attr_device_info = ...
```
+`services.py`
+```python showLineNumbers
+async def _async_get_schedule(call: ServiceCall) -> ServiceResponse:
+ """Get the schedule for a specific range."""
+ if not (entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY_ID])):
+ raise ServiceValidationError("Entry not found")
+ if entry.state is not ConfigEntryState.LOADED:
+ raise ServiceValidationError("Entry not loaded")
+ client = cast(MyConfigEntry, entry).runtime_data
+ ...
+
+@callback
+def async_setup_services(hass: HomeAssistant) -> None:
+ """Register the services."""
+
+ hass.services.async_register(
+ DOMAIN,
+ SERVICE_GET_SCHEDULE,
+ async_get_schedule,
+ _async_get_schedule,
+ schema=SERVICE_GET_SCHEDULE_SCHEMA,
+ supports_response=SupportsResponse.ONLY,
+ description_placeholders={"example_url": "https://schedule.example.com"}
+ )
+```
+
## Exceptions
There are no exceptions to this rule.