How the Home Assistant Service Registry Handles Registration and Calls
The Home Assistant service registry stores callable services in an in-memory dictionary and dispatches calls through schema validation, ServiceCall object creation, and asynchronous job execution via HassJob.
The service registry serves as the central hub that orchestrates all inter-component communication within the Home Assistant ecosystem. Located primarily in homeassistant/core.py and supported by helper utilities in homeassistant/helpers/service.py, this registry manages how integrations expose functionality and how automations, scripts, and the frontend invoke those capabilities. Understanding how the service registry handles service registration and calls is essential for developing robust custom integrations that interact cleanly with Home Assistant's event-driven architecture.
Core Data Structures in homeassistant/core.py
Three classes defined in homeassistant/core.py form the foundation of the service infrastructure.
The Service Class
The Service class (around line 2410) represents a registered callable. It encapsulates:
job: AHassJobwrapping the actual function to executeschema: An optionalvoluptuousschema for data validationsupports_response: Boolean indicating if the service can return datadescription_placeholders: Metadata for documentation generation
The ServiceCall Class
When a service is invoked, the registry creates a ServiceCall instance (defined around line 2445). This object carries:
domainandservice: Identifiers for the target servicedata: The validated parameters passed by the callercontext: AContextobject tracking the call's origin and permissionsreturn_response: Boolean flag indicating whether the caller expects a return value
The ServiceRegistry Class
The ServiceRegistry class (line 2477) maintains the actual storage mechanism via self._services, a dictionary mapping domains to service names to Service objects. This in-memory structure provides O(1) lookup times when dispatching calls.
Registering Services Through the Registry
Integration code rarely interacts with the core registry directly. Instead, developers use helper functions that construct appropriate HassJob wrappers and metadata.
From Integration Code to Registry Storage
Most entity-based integrations use async_register_entity_service from homeassistant/helpers/service.py (around line 1919). This helper accepts:
from homeassistant.helpers.service import async_register_entity_service
async_register_entity_service(
hass,
domain="light",
name="turn_on",
entities=entities,
func="async_turn_on",
schema=vol.Schema({vol.Optional("transition"): vol.Coerce(int)}),
)
The helper builds a service function that iterates over target entities and forwards the registration to hass.services.async_register.
The Registration Flow in ServiceRegistry.async_register
The core registration logic resides in ServiceRegistry.async_register (lines 2670–2690). The method:
- Normalizes domain and service names to lowercase
- Instantiates a
Serviceobject with the providedHassJob, schema, and metadata - Stores the instance in
self._services[domain][service]
self._services.setdefault(domain.lower(), {})[service.lower()] = Service(
func, schema, domain, service, context, supports_response, job_type,
description_placeholders,
)
Executing Service Calls
When automations or scripts trigger services, the registry coordinates validation, context propagation, and asynchronous execution.
The async_call Execution Pipeline
The ServiceRegistry.async_call method (lines 2712–2750) handles the complete dispatch flow:
- Service Lookup: Retrieves the
Serviceinstance fromself._services - Schema Validation: If a schema exists, validates and coerces
service_dataagainst it - Context Building: Creates a
ServiceCallobject encapsulating the request - Job Scheduling: Executes
hass.async_run_hass_job(service.job, call)to run the handler asynchronously
await hass.services.async_call(
domain="light",
service="turn_on",
service_data={"entity_id": "light.kitchen", "brightness": 255},
blocking=True,
context=Context(user_id="admin_123"),
return_response=False,
)
Setting blocking=True pauses the caller until the service completes, while return_response=True captures any data returned by the service handler.
Schema Validation and Response Handling
If the Service instance includes a schema, the registry validates incoming data before creating the ServiceCall object. Validation failures raise exceptions immediately, preventing invalid state changes. When return_response is enabled, the registry captures the result of hass.async_run_hass_job and returns it to the caller; otherwise, the method returns None after confirming execution.
Admin-Only Services and Security Controls
For sensitive operations, async_register_admin_service (lines 4950–4975 in homeassistant/helpers/service.py) wraps handlers with _async_admin_handler. This wrapper inspects the ServiceCall.context to verify the calling user possesses administrator privileges before executing the underlying function. If the check fails, the call aborts with a permission error.
hass.services.async_register(
domain,
service,
partial(_async_admin_handler, hass, HassJob(service_func)),
schema,
supports_response,
)
Caching Service Descriptions
The registry maintains a documentation cache to avoid repeatedly parsing YAML files. The async_get_all_descriptions helper (around line 3530 in homeassistant/helpers/service.py) lazily loads services.yaml files from each integration, storing structured metadata in SERVICE_DESCRIPTION_CACHE. This cache enables the frontend to display user-friendly service descriptions and parameter schemas without filesystem overhead on every call.
Complete Example: Registering and Calling a Custom Service
The following implementation demonstrates the full lifecycle from registration to invocation:
# custom_components/my_integration/__init__.py
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers.service import async_register_admin_service
import voluptuous as vol
DOMAIN = "my_integration"
async def async_setup(hass: HomeAssistant, config: dict) -> bool:
async def handle_set_value(call: ServiceCall):
"""Handle the service call."""
value = call.data.get("value", 0)
hass.states.async_set(f"{DOMAIN}.example_sensor", value)
# Register with admin restrictions and schema validation
async_register_admin_service(
hass,
DOMAIN,
"set_value",
handle_set_value,
schema=vol.Schema({vol.Optional("value"): vol.Coerce(int)}),
)
return True
Invoke the service programmatically:
await hass.services.async_call(
"my_integration",
"set_value",
{"value": 42},
blocking=True,
return_response=False,
)
Or via YAML automation:
service: my_integration.set_value
data:
value: 42
Summary
- The service registry stores all callable services in
ServiceRegistry._services, a nested dictionary mapping domains to service names. - Registration flows through helper functions like
async_register_entity_serviceandasync_register_admin_service, ultimately callingServiceRegistry.async_registerinhomeassistant/core.py. - Service calls trigger the
ServiceRegistry.async_callpipeline, which validates data against schemas, createsServiceCallobjects, and executes handlers viaHassJob. - Security controls wrap sensitive services with admin verification checks before execution.
- Description caching optimizes frontend performance by storing parsed
services.yamlmetadata in memory.
Frequently Asked Questions
What is the difference between async_register_entity_service and async_register_admin_service?
async_register_entity_service targets specific entity instances, automatically iterating over targeted entities and calling the specified method on each one. async_register_admin_service wraps a handler with permission checks, ensuring only users with administrator privileges can execute sensitive operations like configuration changes or system restarts.
How does the service registry validate incoming call data?
The registry validates data using Voluptuous schemas attached during registration. When ServiceRegistry.async_call processes a request, it checks if the Service instance has a schema attribute. If present, the registry validates and coerces the service_data dictionary against this schema before creating the ServiceCall object, raising exceptions for invalid input.
Can a service return data to the caller in Home Assistant?
Yes. Services can return data when registered with supports_response=True and called with return_response=True. The registry captures the result from hass.async_run_hass_job and returns it to the caller. This pattern enables services to provide confirmation data, entity states, or calculation results back to automations or the frontend.
Where are service descriptions stored and cached?
Service descriptions reside in each integration's services.yaml file. The registry caches these descriptions in memory via SERVICE_DESCRIPTION_CACHE, populated by async_get_all_descriptions in homeassistant/helpers/service.py. This caching layer prevents repeated filesystem access when the frontend requests service documentation for the developer tools or automation editors.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →