Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
bba8b74
Update TODO.md
GrandMoff100 Feb 22, 2022
544e657
Update TODO.md
GrandMoff100 Feb 22, 2022
011fc38
Update TODO.md
GrandMoff100 Feb 22, 2022
6a47208
Update TODO.md
GrandMoff100 Feb 22, 2022
b3bac9d
Update TODO.md
GrandMoff100 Feb 22, 2022
4f67f8f
Update README.md
GrandMoff100 Feb 22, 2022
d52f8ea
Update TODO.md
GrandMoff100 Feb 22, 2022
9e5948c
Exclude model client attribute for export and repr
GrandMoff100 Feb 23, 2022
07c3ecd
[MegaLinter] Apply linters fixes
GrandMoff100 Feb 23, 2022
d748ae5
Update README.md
GrandMoff100 Feb 23, 2022
59d0eef
Merge pull request #65 from GrandMoff100/feature/repr-cleanup
GrandMoff100 Feb 23, 2022
6ad1ad8
Added paths argument to workflow trigger event
GrandMoff100 Feb 23, 2022
8ccbdf1
[MegaLinter] Apply linters fixes
GrandMoff100 Feb 23, 2022
b667700
Merge pull request #66 from GrandMoff100/maintainence/python-only-ci
GrandMoff100 Feb 23, 2022
db65222
Added Logbook Entry model to Client
GrandMoff100 Feb 23, 2022
36b578d
Added Logbook Entry to async Client
GrandMoff100 Feb 23, 2022
cceeaa0
Merge pull request #67 from GrandMoff100/feature/logbook_entry_model
GrandMoff100 Feb 23, 2022
50527f6
Fixed dead link in changelog
GrandMoff100 Feb 23, 2022
ce99310
Merge pull request #68 from GrandMoff100/bugs/dead-link-1
GrandMoff100 Feb 23, 2022
22fbffa
Fixed mega-linter bugs.
GrandMoff100 Feb 23, 2022
7f3fd34
Added quotes to prevent word splitting
GrandMoff100 Feb 23, 2022
2a6feeb
Enabled fail_on_error
GrandMoff100 Feb 23, 2022
97a5953
Merge branch 'dev' into bugs/actionlint-bug
GrandMoff100 Feb 23, 2022
814ca3b
Merge pull request #69 from GrandMoff100/bugs/actionlint-bug
GrandMoff100 Feb 23, 2022
64f17a4
Update main.py
GrandMoff100 Feb 24, 2022
6449d5f
Update and rename development.rst to CONTRIBUTING.rst
GrandMoff100 Feb 27, 2022
ae91962
Update README.md
GrandMoff100 Feb 27, 2022
08877f4
Update CODEOWNERS
GrandMoff100 Feb 27, 2022
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
5 changes: 4 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# These owners are the default owners for everything in the repo.
# Unless a later match takes precedence,
# These owners will be requested for review when someone opens a pull request.
* @GrandMoff100
* @GrandMoff100

# Custom CSS Styling
docs/static/css/custom.css @FoxNerdSaysMoo
2 changes: 2 additions & 0 deletions .github/workflows/code_rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ name: Code Standards

on:
push: # Comment this line to trigger action only on pull-requests (not recommended if you don't pay for GH Actions)
paths:
- "**.py"
pull_request:
branches:
- master
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ name: "CodeQL"
on:
push:
branches: [dev]
paths:
- "**.py"
pull_request:
# The branches below must be a subset of the branches above
branches: [dev]
Expand Down
5 changes: 1 addition & 4 deletions .github/workflows/python-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,4 @@ jobs:
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
poetry publish --build \
--username $TWINE_USERNAME \
--password $TWINE_PASSWORD
run: poetry publish --build -u "$TWINE_USERNAME" -p "$TWINE_PASSWORD"
4 changes: 3 additions & 1 deletion .mega-linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ APPLY_FIXES: all # all, none, or list of linter keys
DISABLE:
- PYTHON
- COPYPASTE
DISABLE_LINTERS:
- SPELL_CSPELL
SHOW_ELAPSED_TIME: true
FILEIO_REPORTER: true
RST_FILTER_REGEX_EXCLUDE: "(:resource:`.+`)"
DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass
# DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
## v2.3.0

- Bug fixes (see closed issues between releases)
- Added global request kwargs parameter to Client objects (see [docs](homeassistantapi.rtfd.io/en/latest/api.html#homeassistant_api.Client))
- Added global request kwargs parameter to Client objects (see [docs](https://homeassistantapi.rtfd.io/en/latest/api.html#homeassistant_api.Client))

## v2.2.0

Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
# HomeassistantAPI

![Lines of code](https://img.shields.io/tokei/lines/github/GrandMoff100/HomeassistantAPI?style=for-the-badge)
![PyPI - Downloads](https://img.shields.io/pypi/dm/HomeAssistant-API?style=for-the-badge)
[![PyPI - Downloads](https://img.shields.io/pypi/dm/HomeAssistant-API?style=for-the-badge)](https://pypi.org/project/homeassistant_api)
![GitHub commits since latest release (by date including pre-releases)](https://img.shields.io/github/commits-since/GrandMoff100/HomeassistantAPI/latest/dev?include_prereleases&style=for-the-badge)
![Read the Docs (version)](https://img.shields.io/readthedocs/homeassistantapi/stable?style=for-the-badge)
![GitHub release (latest by date)](https://img.shields.io/github/v/release/GrandMoff100/HomeassistantAPI?style=for-the-badge)
![GitHub release (latest by date)](https://img.shields.io/github/downloads/GrandMoff100/HomeassistantAPI/latest/total?style=for-the-badge)
[![Read the Docs (version)](https://img.shields.io/readthedocs/homeassistantapi?style=for-the-badge)](https://homeassistantapi.readthedocs.io/en/latest/?badge=latest)
[![GitHub release (latest by date)](https://img.shields.io/github/v/release/GrandMoff100/HomeassistantAPI?style=for-the-badge)](https://github.com/GrandMoff100/HomeassistantAPI/releases)

![Home AssistantLogo](https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true)
[![Home Assistant Logo](https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true)](https://home-assistant.io)

Python Wrapper for Homeassistant's [REST API](https://developers.home-assistant.io/docs/api/rest/)

Expand Down
22 changes: 16 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# Todo
# TODOs (A checklist of sorts)

## Features
- [ ] Add caching to Entity.get_state (to make it fetch state automatically)
## Code Features

- [X] Create AsyncClient for integration in async applications and libraries
- [ ] Add Testing Suite/Workflow the runs Home Assistant Core to test library.
- [X] Clean up Model `repr` methods with disabling model field `repr`s.

- [X] Add if statements to all Client methods that call malformed_id and raise MalformedInputError if true.
## Code Bugs

## Documentation
None yet?

## Maintenance

- [ ] Fix workflows to only run when python paths are modified.

## Documentation

- [ ] Add runnable Code Examples.
- [ ] Document project scripts.
- [ ] Document branch naming scheme (i.e. `feature/<some_feature>`, `maintenance/<stuff>`, `bug/<bug-title>`, `docs/<feature/version>`)
2 changes: 1 addition & 1 deletion docs/development.rst → docs/CONTRIBUTING.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
.. _development_page:

*****************
Development
Contributing
*****************

This page is where development related things are.
Expand Down
32 changes: 8 additions & 24 deletions homeassistant_api/_async/asyncclient.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
"""Module for interacting with Home Assistant asyncronously."""
import asyncio
from datetime import datetime
from os.path import join
from typing import Any, AsyncGenerator, Dict, List, Optional, Tuple, Union, cast

import aiohttp
from aiohttp_client_cache import CachedSession

from ..const import DATE_FMT
from ..errors import APIConfigurationError, MalformedDataError, RequestError
from ..mixins import JsonProcessingMixin
from ..models import Domain, Event, History, State
from ..models import Domain, Event, History, LogbookEntry, State
from ..processing import Processing
from ..rawapi import RawWrapper
from .models import AsyncEntity, AsyncGroup
Expand Down Expand Up @@ -85,28 +83,14 @@ async def async_api_config(self) -> Dict[str, Any]:

async def async_logbook_entries(
self,
filter_entity: AsyncEntity = None,
start_timestamp: Union[str, datetime] = None, # Defaults to 1 day before
end_timestamp: Union[str, datetime] = None,
) -> Tuple[Dict[str, Any], ...]:
*args,
**kwargs,
) -> AsyncGenerator[LogbookEntry, None]:
"""Returns a list of logbook entries from homeassistant."""
params: Dict[str, str] = {}
if filter_entity is not None:
params.update(entity=filter_entity.entity_id)
if end_timestamp is not None:
if isinstance(end_timestamp, datetime):
end_timestamp = end_timestamp.strftime(DATE_FMT)
params.update(end_time=end_timestamp)
if start_timestamp is not None:
if isinstance(start_timestamp, datetime):
formatted_timestamp = start_timestamp.strftime(DATE_FMT)
url = join("logbook", formatted_timestamp)
else:
url = "logbook"
return cast(
Tuple[Dict[str, Any], ...],
await self.async_request(url, params=params),
)
params, url = self.prepare_get_logbook_entry_params(*args, **kwargs)
data = await self.async_request(url, params=params)
for entry in data:
yield LogbookEntry.parse_obj(entry)

async def async_get_entity_histories(
self,
Expand Down
4 changes: 3 additions & 1 deletion homeassistant_api/_async/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
from os.path import join
from typing import TYPE_CHECKING, Any, Dict, Optional, cast

from pydantic import Field

from ...models import BaseModel, History, State

if TYPE_CHECKING:
Expand All @@ -12,7 +14,7 @@ class AsyncGroup(BaseModel):
"""Represents the groups that entities belong to"""

group_id: str
client: "Client"
client: "Client" = Field(exclude=True, repr=False)
entities: Dict[str, "AsyncEntity"] = {}

def add_entity(self, entity_slug: str, state: State) -> None:
Expand Down
4 changes: 3 additions & 1 deletion homeassistant_api/_async/models/events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""Event Model File"""
from typing import TYPE_CHECKING, Dict, cast

from pydantic import Field

from ...models import BaseModel

if TYPE_CHECKING:
Expand All @@ -17,7 +19,7 @@ class AsyncEvent(BaseModel):

event_type: str
listener_count: int
client: "Client"
client: "Client" = Field(exclude=True, repr=False)

async def async_fire(self, **event_data) -> str:
"""Fires the event type in homeassistant. Ex. `on_startup`"""
Expand Down
6 changes: 3 additions & 3 deletions homeassistant_api/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def process_services_json(self, json: Dict[str, Any]) -> Domain:
@staticmethod
def process_state_json(json: Dict[str, Any]) -> State:
"""Constructs State model from json data"""
return State(**json)
return State.parse_obj(json)

def process_event_json(self, json: Dict[str, Any]) -> Event:
"""Constructs Event model from json data"""
Expand All @@ -43,11 +43,11 @@ async def async_process_services_json(
@staticmethod
async def async_process_state_json(json: Dict[str, Any]) -> State:
"""Constructs State model from json data"""
return State(**json)
return State.parse_obj(json)

async def async_process_event_json(
self,
json: Dict[str, Any],
) -> AsyncEvent:
"""Constructs Event model from json data"""
"""Constructs Event model from json data."""
return AsyncEvent(**json, client=self)
1 change: 1 addition & 0 deletions homeassistant_api/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
from .entity import Entity, Group
from .events import Event
from .history import History
from .logbook import LogbookEntry
from .states import State
4 changes: 3 additions & 1 deletion homeassistant_api/models/domains.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
"""File for Service and Domain data models"""
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple

from pydantic import Field

from .base import BaseModel
from .states import State

Expand All @@ -12,7 +14,7 @@ class Domain(BaseModel):
"""Model representing the domain that services belong to."""

domain_id: str
client: "Client"
client: "Client" = Field(exclude=True, repr=False)
services: Dict[str, "Service"] = {}

def add_service(self, service_id: str, **data) -> None:
Expand Down
4 changes: 3 additions & 1 deletion homeassistant_api/models/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from os.path import join
from typing import TYPE_CHECKING, Any, Dict, Optional, cast

from pydantic import Field

from .base import BaseModel
from .history import History
from .states import State
Expand All @@ -15,7 +17,7 @@ class Group(BaseModel):
"""Represents the groups that entities belong to."""

group_id: str
client: "Client"
client: "Client" = Field(exclude=True, repr=False)
entities: Dict[str, "Entity"] = {}

def add_entity(self, entity_slug: str, state: State) -> None:
Expand Down
4 changes: 3 additions & 1 deletion homeassistant_api/models/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from typing import TYPE_CHECKING, Dict, cast

from pydantic import Field

from .base import BaseModel

if TYPE_CHECKING:
Expand All @@ -18,7 +20,7 @@ class Event(BaseModel):

event_type: str
listener_count: int
client: "Client"
client: "Client" = Field(exclude=True, repr=False)

def fire(self, **event_data) -> str:
"""Fires the corresponding event in Home Assistant."""
Expand Down
17 changes: 17 additions & 0 deletions homeassistant_api/models/logbook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Module for the Logbook Entry model."""
from datetime import datetime
from typing import Optional

from .base import BaseModel


class LogbookEntry(BaseModel):
"""Model representing"""

when: datetime
name: str
entity_id: str
state: Optional[str] = None
domain: Optional[str] = None
context_id: Optional[str] = None
icon: Optional[str] = None
4 changes: 2 additions & 2 deletions homeassistant_api/processing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import inspect
import json
from typing import Callable, Dict, Tuple, Union
from typing import Callable, ClassVar, Dict, Tuple, Union

import simplejson
from aiohttp import ClientResponse
Expand All @@ -26,7 +26,7 @@ class Processing(BaseModel):
"""Uses to processor functions to convert json data into common python data types."""

response: Union[Response, CachedResponse, ClientResponse, AsyncCachedResponse]
_processors: Dict[str, Tuple[Callable, ...]] = {}
_processors: ClassVar[Dict[str, Tuple[Callable, ...]]] = {}

@staticmethod
def processor(mimetype: str):
Expand Down
31 changes: 28 additions & 3 deletions homeassistant_api/rawapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import os
from datetime import datetime
from typing import Dict, Optional, Tuple
from typing import Dict, Optional, Tuple, Union

from .const import DATE_FMT
from .errors import MalformedInputError
Expand All @@ -26,8 +26,6 @@ def __init__(
) -> None:
if global_request_kwargs is None:
global_request_kwargs = {}
if not self.api_url.endswith("/"):
self.api_url += "/"
if cache_backend is None:
cache_backend = "memory"
if cache_expire_after is None:
Expand All @@ -39,6 +37,9 @@ def __init__(
self.cache_backend = cache_backend
self.cache_expire_after = cache_expire_after

if not api_url.endswith("/"):
self.api_url += "/"

def endpoint(self, path: str) -> str:
"""Joins the api base url with a local path to an absolute url"""
return os.path.join(self.api_url, path)
Expand Down Expand Up @@ -132,3 +133,27 @@ def prepare_get_entity_histories_params(
else:
url = "history/period"
return params, url

@staticmethod
def prepare_get_logbook_entry_params(
filter_entity: Optional[Entity] = None,
start_timestamp: Optional[
Union[str, datetime]
] = None, # Defaults to 1 day before
end_timestamp: Optional[Union[str, datetime]] = None,
) -> Tuple[Dict[str, str], str]:
"""Prepares the query string and url path for retrieving logbook entries."""
params: Dict[str, str] = {}
if filter_entity is not None:
params.update(entity=filter_entity.entity_id)
if end_timestamp is not None:
if isinstance(end_timestamp, datetime):
end_timestamp = end_timestamp.strftime(DATE_FMT)
params.update(end_time=end_timestamp)
if start_timestamp is not None:
if isinstance(start_timestamp, datetime):
formatted_timestamp = start_timestamp.strftime(DATE_FMT)
url = os.path.join("logbook", formatted_timestamp)
else:
url = "logbook"
return params, url
Loading