Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ venv/
.replit
.breakpoints

# Informal testing
main.py

# Cache files
*.sqlite

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/GrandMoff100/HomeassistantAPI?style=for-the-badge)](https://github.com/GrandMoff100/HomeassistantAPI/releases)

<a href="https://home-assistant.io">
<img src="https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true" width="60%">
<img src="https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true" width="80%">
</a>

## Python wrapper for Homeassistant's [Websocket API](https://developers.home-assistant.io/docs/api/websocket/) and [REST API](https://developers.home-assistant.io/docs/api/rest/)
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Rather than the default behavior, which is saving the cache to memory or not at
If you want to persist your requests cache you can pass your own custom cached session to :py:class:`Client`'s init method.
You can pass a variety of options to your cached session like how fast to expire the cache, where to cache it (the cache backend), and what to do when the cache is expired.

Depending on whether you are using this in an async of sync project you will want to use either :py:class:`aiohttp_client_cache.backends.CachedSession` or :py:class:`requests_cache.CachedSession` respectively.
Depending on whether you are using this in an async or sync project you will want to use either :py:class:`aiohttp_client_cache.backends.CachedSession` or :py:class:`requests_cache.CachedSession` respectively.
See the docs for `requests_cache <https://requests-cache.readthedocs.io/en/latest/>`__ and `aiohttp_client_cache <https://aiohttp-client-cache.readthedocs.io/en/latest/>`__ for how to implement these backends, options, and much more.

You can simply pass them to your client like so.
Expand Down
2 changes: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Code Reference
.. automodule:: homeassistant_api
:platform: Linux, Windows, MacOS
:inherited-members:
:exclude-members: model_json_schema, model_copy, model_rebuild, model_dump, construct, copy, dict, from_orm, json, parse_file, model_validate, parse_raw, parse_str, parse_url, schema, schema_json, schema_yaml, schema_yml, to_orm, update_forward_refs, validate, validate_file, validate_obj, validate_raw, validate_str, validate_url, model_validate_strings, model_validate_json, model_validate, model_post_init, model_parametrized_name, model_extra, model_fields_set, model_dump_json, model_construct, model_computed_fields
:exclude-members: model_json_schema, model_copy, model_rebuild, model_dump, construct, copy, dict, from_orm, json, parse_file, model_validate, parse_raw, parse_obj, parse_str, parse_url, schema, schema_json, schema_yaml, schema_yml, to_orm, update_forward_refs, validate, validate_file, validate_obj, validate_raw, validate_str, validate_url, model_validate_strings, model_validate_json, model_validate, model_post_init, model_parametrized_name, model_extra, model_fields_set, model_dump_json, model_construct, model_computed_fields
4 changes: 2 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
# -- Project information -----------------------------------------------------

project = "Homeassistant API"
copyright = "2024, Nathan Larsen" # pylint: disable=redefined-builtin
author = "Nate Larsen"
copyright = "2023-2025, Nathan Larsen" # pylint: disable=redefined-builtin
author = "Nathan Larsen"

# The full version, including alpha/beta/rc tags
with open("../pyproject.toml") as f:
Expand Down
5 changes: 3 additions & 2 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
Welcome to Homeassistant API!
=============================

Homeassistant API is a pythonic module that interacts with `Homeassistant's REST API integration <https://developers.home-assistant.io/docs/api/rest>`_.
You can use it to remotely control your Home Assistant like getting entity states, triggering services, etc.
Homeassistant API is a pythonic module that interacts with `Homeassistant's REST API integration <https://developers.home-assistant.io/docs/api/rest>`_ and Homeassistant's `Websocket API <https://developers.home-assistant.io/docs/api/websocket>`_.
You can use it to remotely control your Home Assistant to do things like turn on lights, change the temperature, or listen for when the garage door opens.

Index
----------
Expand All @@ -30,6 +30,7 @@ Features
----------

- Full consumption of the Home Assistant REST API endpoints.
- Full consumption of the Home Assistant Websocket API (all of the documented commands and some undocumented ones)
- Convenient Pydantic Models for data validation.
- Syncrononous and Asynchronous support for integrating with all applications and/or libraries.
- Modular design for intuitive readability.
Expand Down
90 changes: 86 additions & 4 deletions docs/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,19 @@ Usage
The Basics...
#################

This library is centered around the :py:class:`Client` class.
This library is centered around the :py:class:`Client` and :py:class:`WebsocketClient` classes.
Once you have have your api base url and Long Lived Access Token from Home Assistant we can start to do stuff.
The rest of this guide assumes you have the :py:class:`Client` saved to a :code:`client` variable.
The rest of this guide assumes you have the :py:class:`Client` saved to a :code:`client` variable or a :py:class:`WebsocketClient` saved to a :code:`ws_client` variable.
Most of these examples require some integrations to be setup inside Home Assistant for the examples to actually work.
The most commonly used features of this library include triggering services and getting and modifying entity states.


.. code-block:: python
:linenos:

import os
from homeassistant_api import Client

URL = '<API BASE URL>'
URL = '<API BASE URL>' # Example: 'https://foobarhomeassistant.duckdns.org:8123/api'
TOKEN = '<LONG LIVED ACCESS TOKEN>'

# Assigns the Client object to a variable and checks if it's running.
Expand All @@ -31,6 +30,18 @@ The most commonly used features of this library include triggering services and
# Triggers the light.turn_on service on the entity `light.my_living_room_light`


.. code-block:: python
:linenos:

from homeassistant_api import WebsocketClient

WS_URL = '<WS API BASE URL>' # Example: 'https://foobarhomeassistant.duckdns.org:8123/api/websocket'
TOKEN = '<LONG LIVED ACCESS TOKEN>'

with WebsocketClient(WS_URL, TOKEN) as ws_client: # opens a websocket connection to Home Assistant
print(ws_client.render_template("{{ states('sensor.my_sensor') }}"))


.. code-block:: python
:linenos:

Expand Down Expand Up @@ -66,6 +77,15 @@ Services

changed_states = light.toggle(entity_id="light.light_bulb_1")

.. code-block:: python

climate = ws_client.get_domain("climate")

print(climate.services)
# {'set_temperature': Service(service_id='set_temperature', name='Set temperature', description='Set the target temperature for a climate entity.\n', ...

changed_states = climate.set_temperature(entity_id="climate.my_thermostat", temperature=72)

Entities
*************

Expand Down Expand Up @@ -95,12 +115,15 @@ Entities
door.set_state(State(state="My new state", attributes={"open_height": "5ft"}))
# <State "My new state" entity_id="cover.garage_door">

## All of these methods can be used with the WebsocketClient as well [except for set_state because the WS API doesn't support it :((( ].

Using Client with :code:`async`/:code:`await`
*************************************************
Are you wondering if you can use :code:`homeassistant_api` using Python's :code:`async`/:code:`await` syntax?
Good news! You can!

(You can't use the WebsocketClient with :code:`async`/:code:`await` yet because we haven't implemented it yet.)

Async Services
********************
.. code-block:: python
Expand Down Expand Up @@ -159,8 +182,67 @@ Async Entities
# <State "My new state" entity_id="cover.garage_door">


Using Events (Listening and Firing)
*****************************************

.. code-block:: python

from homeassistant_api import WebsocketClient

WS_URL = '<WS API BASE URL>' # Example: 'https://foobarhomeassistant.duckdns.org:8123/api/websocket'
TOKEN = '<LONG LIVED ACCESS TOKEN>'

with WebsocketClient(WS_URL, TOKEN) as ws_client:
with ws_client.listen_events() as events:
for event in events:
print(event)

# Or if you want to listen for a specific event type until dinner time.
with ws_client.listen_events('state_changed') as events:
for event in events:
print(event)
if event.data.entity_id == 'myalarmclock.dinner_time' and event.data.new_state.state == 'now':
break

# Or if you want to listen for just 10 events.
with ws_client.listen_events("my_event") as events:
for _, event in zip(range(10), events):
print(event)

# Alternatively for just one event.
with ws_client.listen_events("my_event") as events:
event = next(events)
print(event)

# Now to fire an event.
ws_client.fire_event("my_event", my_arg="my_value")


Listening for Triggers
**************************

.. code-block:: python

from homeassistant_api import WebsocketClient

with WebsocketClient(WS_URL, TOKEN) as ws_client:
with ws_client.listen_triggers() as triggers: # see WebsocketClient.listen_triggers for more info.
for trigger in triggers:
print(trigger)

# Another more specific example, listening for event triggers.
with ws_client.listen_triggers("event", event_type="my_event") as triggers:
ws_client.fire_event("my_event", my_arg="my_value")

for trigger in triggers:
print(trigger.variables.my_arg) # This is the value of my_arg from the event fired above.

# Another one, listening for time triggers.
future = ws_client.get_rendered_template(
"{{ (now() + timedelta(seconds=1)).strftime('%H:%M:%S') }}"
)
with ws_client.listen_trigger("time", at=future) as triggers: # `at` can be HH:MM or HH:MM:SS
print(next(triggers))

What's Next?
#############
Expand Down
50 changes: 25 additions & 25 deletions homeassistant_api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,40 @@
__all__ = (
"Client",
"State",
"Context",
"Domain",
"Service",
"History",
"Group",
"Event",
"Entity",
"Domain",
"Processing",
"History",
"Event",
"LogbookEntry",
"APIConfigurationError",
"EndpointNotFoundError",
"HomeassistantAPIError",
"MalformedDataError",
"MalformedInputError",
"MethodNotAllowedError",
"ParameterMissingError",
"RequestError",
"UnauthorizedError",
"WebsocketClient",
"AuthInvalid",
"AuthOk",
"AuthRequired",
"ResultResponse",
"ErrorResponse",
"PingResponse",
"EventResponse",
)

from .client import Client
from .errors import (
APIConfigurationError,
EndpointNotFoundError,
HomeassistantAPIError,
MalformedDataError,
MalformedInputError,
MethodNotAllowedError,
ParameterMissingError,
RequestError,
UnauthorizedError,
from .models.domains import Domain, Service
from .models.entity import Entity, Group
from .models.events import Event
from .models.history import History
from .models.logbook import LogbookEntry
from .models.states import Context, State
from .models.websocket import (
AuthInvalid,
AuthOk,
AuthRequired,
ErrorResponse,
EventResponse,
PingResponse,
ResultResponse,
)
from .models import Domain, Entity, Event, Group, History, LogbookEntry, Service, State
from .processing import Processing
from .websocket import WebsocketClient

Domain.model_rebuild()
Expand Down
2 changes: 1 addition & 1 deletion homeassistant_api/rawclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@

class RawClient(RawBaseClient):
"""
The base object for interacting with Homeassistant.
The base object for interacting with Homeassistant via the REST API.

:param api_url: The location of the api endpoint. e.g. :code:`http://localhost:8123/api` Required.
:param token: The refresh or long lived access token to authenticate your requests. Required.
Expand Down
1 change: 0 additions & 1 deletion homeassistant_api/rawwebsocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
ResultResponse,
)


logger = logging.getLogger(__name__)


Expand Down
Loading