From 73ed60008b40b59884ffa073c0346fd156d9b4ef Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Wed, 16 Feb 2022 05:49:10 +0000 Subject: [PATCH 01/11] Added convenient script for developing sphinx docs --- scripts/run_docs_dev | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100755 scripts/run_docs_dev diff --git a/scripts/run_docs_dev b/scripts/run_docs_dev new file mode 100755 index 00000000..d4f1ad00 --- /dev/null +++ b/scripts/run_docs_dev @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +rm -rf build +sphinx-build docs build +cd build +python -m http.server +cd ../ \ No newline at end of file From 2ea1ef12e94f501f2a329ee264be4685444ee4b6 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Wed, 16 Feb 2022 05:56:24 +0000 Subject: [PATCH 02/11] Added pre v3.0.0 changelog entries. --- CHANGELOG.md | 42 +++++++++++++++++++++++++----------------- pyproject.toml | 2 +- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0df7dc11..4a7309cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,28 +1,36 @@ # Changelog -**v2.0.0** -- Added Data Models -- Added Documentation -- Added functions for all endpoints +**v3.0.0** +- Added Rigorous CI/CD tools, i.e. `black`, `isort`, `mypy`, `pre-commit`, `pylint`, `flake8`. +- Renamed `AsyncClient` methods with `async_` convention. +- `Client` and `AsyncClient` can be initialized without confirming the API's status. +- `Client` and `AsyncClient` are now both context managers that function the exact same. +- Both clients now share previously redundant model conversion methods. +- Reversed CHANGELOG order (most recent first). -**v2.1.0** -- Added Event support - -**v2.2.0** -- Implemented async support with `homeassistant_api._async.AsyncClient` +**v2.4.0.post2** +- Fixed wrong check in malformed_id function -**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)) +**v2.4.0.post1** +- Replaced `text/plain` with `application/octet-stream` in docs and processing module. +- Added message content to UnrecognizedStatusCodeError to help with user debugging **v2.4.0** - Bug fixes (see closed issues between releases) - Added a processing framework for hooking into mimetype processing - Fixed some issues with some ``AsyncClient`` methods -**v2.4.0.post1** -- Replaced `text/plain` with `application/octet-stream` in docs and processing module. -- Added message content to UnrecognizedStatusCodeError to help with user debugging +**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)) -**v2.4.0.post2** -- Fixed wrong check in malformed_id function +**v2.2.0** +- Implemented async support with `homeassistant_api._async.AsyncClient` + +**v2.1.0** +- Added Event support + +**v2.0.0** +- Added Data Models +- Added Documentation +- Added functions for all endpoints diff --git a/pyproject.toml b/pyproject.toml index d418dbfd..0459f18e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "Home Assistant API" -version = "2.4.1" +version = "3.0.0" description = "Python Wrapper for Homeassistant's REST API" authors = ["GrandMoff100 "] license = "GPL-3.0-or-later" From b845496c6b9f086db6a506708ce55e660b352658 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Wed, 16 Feb 2022 06:34:37 +0000 Subject: [PATCH 03/11] Added initial Dev Docs --- .gitpod.yml | 6 ++---- docs/dev_docs/contribution.rst | 2 ++ docs/dev_docs/currently.rst | 12 +++++++++++ docs/dev_docs/guidelines.rst | 3 +++ docs/dev_docs/procedures.rst | 3 +++ docs/dev_docs/tipsandtricks.rst | 3 +++ docs/development.rst | 19 +++++++++++++++++ docs/faq.rst | 7 ------- docs/index.rst | 36 ++++++++++++++++++++------------- docs/quickstart.rst | 2 ++ 10 files changed, 68 insertions(+), 25 deletions(-) create mode 100644 docs/dev_docs/contribution.rst create mode 100644 docs/dev_docs/currently.rst create mode 100644 docs/dev_docs/guidelines.rst create mode 100644 docs/dev_docs/procedures.rst create mode 100644 docs/dev_docs/tipsandtricks.rst create mode 100644 docs/development.rst delete mode 100644 docs/faq.rst diff --git a/.gitpod.yml b/.gitpod.yml index 57431a73..7ddb8314 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -6,13 +6,11 @@ tasks: pip install poetry poetry config virtualenvs.create false poetry install - - command: | - vscode: extensions: - ms-python.python - matangover.mypy -ports: + - rogalmic.bash-debugports: - port: 8000 visibility: public - onOpen: ignore \ No newline at end of file + onOpen: ignore diff --git a/docs/dev_docs/contribution.rst b/docs/dev_docs/contribution.rst new file mode 100644 index 00000000..53809eb5 --- /dev/null +++ b/docs/dev_docs/contribution.rst @@ -0,0 +1,2 @@ +Contribution Ideas +------------------- diff --git a/docs/dev_docs/currently.rst b/docs/dev_docs/currently.rst new file mode 100644 index 00000000..c1f62142 --- /dev/null +++ b/docs/dev_docs/currently.rst @@ -0,0 +1,12 @@ +Current Project Status +======================= + + +TODOs +------------------- + +Roadmap +------------------- + +CHANGELOG +------------------- diff --git a/docs/dev_docs/guidelines.rst b/docs/dev_docs/guidelines.rst new file mode 100644 index 00000000..407b1d27 --- /dev/null +++ b/docs/dev_docs/guidelines.rst @@ -0,0 +1,3 @@ + +Guidelines +------------------- \ No newline at end of file diff --git a/docs/dev_docs/procedures.rst b/docs/dev_docs/procedures.rst new file mode 100644 index 00000000..83f14553 --- /dev/null +++ b/docs/dev_docs/procedures.rst @@ -0,0 +1,3 @@ + +Procedures +------------------- diff --git a/docs/dev_docs/tipsandtricks.rst b/docs/dev_docs/tipsandtricks.rst new file mode 100644 index 00000000..6bb4376a --- /dev/null +++ b/docs/dev_docs/tipsandtricks.rst @@ -0,0 +1,3 @@ + +Tips and Tricks +------------------- diff --git a/docs/development.rst b/docs/development.rst new file mode 100644 index 00000000..40dac663 --- /dev/null +++ b/docs/development.rst @@ -0,0 +1,19 @@ +.. _development_page: + +***************** +Development +***************** + +This page is where development related things are. +See below. + +.. toctree:: + :glob: + + dev_docs/contribution + dev_docs/guidelines + dev_docs/procedures + dev_docs/tipsandtricks + dev_docs/currently + + diff --git a/docs/faq.rst b/docs/faq.rst deleted file mode 100644 index 2e20afa6..00000000 --- a/docs/faq.rst +++ /dev/null @@ -1,7 +0,0 @@ -FAQs -***** - -We want to add some Frequently Asked Questions but frankly questions haven't been asked frequently enough. -Until then this page will be left empty. :( - -If you have questions open a discussion or issue on the :resource:`repository `! :) diff --git a/docs/index.rst b/docs/index.rst index df0cfb55..6a745823 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -20,23 +20,21 @@ Index Home quickstart usage - faq - credits + development Features ---------- - Full consumption of the Homeassistant REST API endpoints -- Convenient classes that represent data from the API -- Asynchronous support for integration in async applications or libraries +- Convenient classes that represent data from the Home Assistant REST API +- Syncrononous and Asynchronous support for integrating in with all applications and/or libraries - Modular design for intuitive readability Getting Started ------------------- -Is this your first time using the library? This is the place to get started! - +Is this your first time using the library? Start with our :ref:`Quickstart Section ` Example --------- @@ -44,20 +42,30 @@ Example .. literalinclude:: ../examples/basic.py :language: python -Many more examples are available in the :resource:`repository `. Feel feel to open a pull request and add your own! See Contributing Guidelines +Want more? +Many more examples are available in the :resource:`repository `. +Feel feel to open a pull request and add your own! +See the :ref:`Contributing Section `. Code Reference --------------- -View the documentation for each class and function :doc:`here `. +View the documentation for each class and method :doc:`here `. + +.. _contributing_section: -Contributing -------------- +Contributing Guidelines +-------------------------- We very warmly welcome contributions. -If you have an idea or some code you want to add to the project please fork :resource:`the repository `, make your changes, and open a pull request. -Most likely your changes will get merged if your code passes flake8 without any errors, and adds some functionality to the project. -We'd love to incorporate your unique ideas and perspective! +This library has come a long since its one-file humble beginning as a Saturday afternoon project with some buddies. +But there is still much much much more to do! Which is exciting! +If you're a developer that has an idea, suggestion or just wants to be helpful. +See our *newly minted* :ref:`Development page ` for contribution ideas, guidelines, procedures and what to expect with your PR. +Happy developing! We hope to see your PRs soon. + + -We would love to give a special shoutout to ` https://github.com/FoxNerdSaysMoo` for contributions to some of the awesome theme styling on these docs! +.. + We would love to give a special shoutout to `FoxNerdSaysMoo ` for contributions to some of the awesome theme styling on these docs! diff --git a/docs/quickstart.rst b/docs/quickstart.rst index b6150575..a4827b86 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -1,3 +1,5 @@ +.. _quickstart: + *********** Quickstart *********** From d0b97c95b46deb1a60be864c825d723774f75965 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Wed, 16 Feb 2022 06:52:43 +0000 Subject: [PATCH 04/11] Reword quickstart --- .gitpod.yml | 3 ++- docs/index.rst | 7 +++---- docs/quickstart.rst | 43 +++++++++++++++++++++++-------------------- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/.gitpod.yml b/.gitpod.yml index 7ddb8314..3e0cb439 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -10,7 +10,8 @@ vscode: extensions: - ms-python.python - matangover.mypy - - rogalmic.bash-debugports: + - rogalmic.bash-debug +ports: - port: 8000 visibility: public onOpen: ignore diff --git a/docs/index.rst b/docs/index.rst index 6a745823..8e17341b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -59,13 +59,12 @@ Contributing Guidelines -------------------------- We very warmly welcome contributions. -This library has come a long since its one-file humble beginning as a Saturday afternoon project with some buddies. -But there is still much much much more to do! Which is exciting! -If you're a developer that has an idea, suggestion or just wants to be helpful. +This library has come a long way since its one-file humble beginning. On a Saturday afternoon with some buddies. +But while miuch has been done already there is still much much much more to do! Which is exciting! +If you're a developer that has an idea, suggestion or just wants to be helpful because you're nice. See our *newly minted* :ref:`Development page ` for contribution ideas, guidelines, procedures and what to expect with your PR. Happy developing! We hope to see your PRs soon. - .. We would love to give a special shoutout to `FoxNerdSaysMoo ` for contributions to some of the awesome theme styling on these docs! diff --git a/docs/quickstart.rst b/docs/quickstart.rst index a4827b86..0e763e98 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -9,21 +9,20 @@ Prerequisites Homeassistant --------------- -Before using this library, you need to have Homeassistant OS running on a device. -Something like a Rasberry Pi or spare laptop. -If you don't want to do that you can setup a Homeassistant container on your laptop or desktop with docker. +Before using this library, you need to have Homeassistant OS or Home Assistant Core running on a device (i.e. Rasberry Pi, Spare Laptop, etc.), Docker container, or `Home Assistant development environment`. See `here `__ for how to install the installation right for you. - - -Configuring the API in Homeassistant +Configuring the REST API Server in Homeassistant ====================================== Enable the :code:`api` integration in Homeassistant ------------------------------------------------------ -This library requires that you enable the :code:`api` integration on your Homeassistant if you are familiar with setting up integrations. -The :code:`api` integration is also enabled when you enable the :code:`default_config` integration. - +This library requires that the :code:`api` integration on your Homeassistant is enabled. +It is enabled by default with the :code:`default_config` integration. +But if by chance you have disabled :code:`default_config` you need to enable :code:`api`. +Which requires the :code:`http` integration as well. +(Again most likely alreayd enabled on most installations.) +If you are not sure if it is enabled or not, chances are if your frontend is enabled, so is your API Server. Access Token -------------- @@ -51,7 +50,7 @@ Installation with pip is really easy and will install the dependencies this proj $ pip install homeassistant_api # To install the latest dev version - $ pip install git+https://github.com/GrandMoff100/HomeassistantAPI/tree/dev + $ pip install git+https://github.com/GrandMoff100/HomeassistantAPI # Example of installing a pre-release $ pip install homeassistant_api==2.0.0a1 @@ -60,18 +59,21 @@ Installation with pip is really easy and will install the dependencies this proj Installating with :code:`git` ---------------------------------- -To install with git we're going to clone the repository and then run setup.py like so. +To install with git we're going to clone the repository and then run :code:`$ poetry install` like so. .. code-block:: bash # Clone with git git clone https://github.com/GrandMoff100/HomeassistantAPI - - # Switch current working directory to the repository - cd HomeassistantAPI - # Run setup.py - python setup.py install + # CD into your project + cd + + # Install poetry + python -m pip install poetry + + # Run poetry install + python -m poetry install ~/HomeAssistantAPI Then you should be all set to start using the project! If run into any problems open an issue on our github :resource:`issue tracker ` @@ -79,7 +81,8 @@ Then you should be all set to start using the project! If run into any problems Example Usages ================ -Some examples applications of this project include integrating it into a console executable, flask application or just a regular python script. -You can start a project that allows you to use this from the command line. -Or add it to a discord bot to manage your homeassistant from inside discord (you might want to use AsyncClient if you do) -In any event, the possibilities are endless, so go make some cool stuff and share it with us! +Some examples applications of this project include integrating it into a another library, flask application or just a regular python script. +Maybe you want to start a project that allows you to use your homeassistant from your command line but some sassy responses. +Or maybe add it to a discord bot to manage your homeassistant from inside discord (you might want to use AsyncClient if you do, *hint hint wink wink nudge nudge*) +In any event, the possibilities are endless, so go make some cool stuff and share it with us on the :resource:`repository `! +We hope to see your project soon! From b844ee4dcf89a4c71537fd8a63ba5b20a0a28178 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Sun, 20 Feb 2022 04:17:03 +0000 Subject: [PATCH 05/11] Add features to index and update basic example --- docs/index.rst | 3 ++- docs/quickstart.rst | 2 +- examples/basic.py | 16 +++++++--------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 8e17341b..a88d8a1c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -27,9 +27,10 @@ Features ---------- - Full consumption of the Homeassistant REST API endpoints -- Convenient classes that represent data from the Home Assistant REST API +- Convenient Pydantic Models for data validation - Syncrononous and Asynchronous support for integrating in with all applications and/or libraries - Modular design for intuitive readability +- Request caching for efficiencient repeated requests. Getting Started ------------------- diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 0e763e98..39b6f1cd 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -13,7 +13,7 @@ Before using this library, you need to have Homeassistant OS or Home Assistant C See `here `__ for how to install the installation right for you. Configuring the REST API Server in Homeassistant -====================================== +======================================================= Enable the :code:`api` integration in Homeassistant ------------------------------------------------------ diff --git a/examples/basic.py b/examples/basic.py index ffb8dba0..898483b2 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,15 +1,13 @@ from homeassistant_api import Client -api_url = "" # Something like http://localhost:8123/api -token = "" # Used to aunthenticate yourself with homeassistant -# See the documentation on how to obtain a Long Lived Access Token - - -client = Client(api_url, token) # Creates main object +api_url = "https://larsen-hassio.duckdns.org:8123/api" # Something like http://localhost:8123/api +token = os.getenv("HOMEASSISTANT_TOKEN") # Used to aunthenticate yourself with homeassistant +# See the documentation on how to obtain a Long Lived Access Token -light = client.get_domains().light # gets the light domain from homeassistant +with Client(api_url, token) as client: # Create Client objecty and check that its running. + light = client.get_domain("light") -# Tells homeassistant to trigger the turn_on service on the given entity_id -light.turn_on.trigger(entity_id="light.front_room") + # Tells homeassistant to trigger the turn_on service on the given entity_id + light.turn_on(entity_id="light.front_room") From 051e6b6d69aba0dc535dde530af04b2502d85240 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Mon, 21 Feb 2022 03:50:40 +0000 Subject: [PATCH 06/11] Made caching optional --- README.md | 2 +- docs/{processing.rst => advanced.rst} | 8 +- docs/api.rst | 14 ++-- docs/index.rst | 32 ++++---- docs/quickstart.rst | 33 ++++----- docs/usage.rst | 90 +++++++++++++---------- examples/basic.py | 16 ++-- examples/myq_grarage_door.py | 6 +- homeassistant_api/__init__.py | 9 +++ homeassistant_api/_async/asyncclient.py | 39 ++++++---- homeassistant_api/_async/models/entity.py | 8 +- homeassistant_api/_async/models/events.py | 4 +- homeassistant_api/client.py | 2 +- homeassistant_api/errors.py | 4 +- homeassistant_api/models/__init__.py | 1 + homeassistant_api/models/base.py | 14 ++++ homeassistant_api/models/domains.py | 30 ++++++-- homeassistant_api/models/entity.py | 7 +- homeassistant_api/models/events.py | 4 +- homeassistant_api/models/history.py | 3 +- homeassistant_api/models/states.py | 4 +- homeassistant_api/processing.py | 11 +-- homeassistant_api/rawclient.py | 38 ++++++---- pyproject.toml | 3 +- 24 files changed, 224 insertions(+), 158 deletions(-) rename docs/{processing.rst => advanced.rst} (94%) create mode 100644 homeassistant_api/models/base.py diff --git a/README.md b/README.md index 73b579fc..028c1c90 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ![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) -![Homeassistant Logo](https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true) +![Home AssistantLogo](https://github.com/GrandMoff100/HomeAssistantAPI/blob/7edb4e6298d37bda19c08b807613c6d351788491/docs/images/homeassistant-logo.png?raw=true) Python Wrapper for Homeassistant's [REST API](https://developers.home-assistant.io/docs/api/rest/) diff --git a/docs/processing.rst b/docs/advanced.rst similarity index 94% rename from docs/processing.rst rename to docs/advanced.rst index dc0dfdc2..aa664347 100644 --- a/docs/processing.rst +++ b/docs/advanced.rst @@ -1,6 +1,10 @@ +******************* +Advanced Section +******************* + Response Processing ********************** -Homeassistant API uses functions called processors. +Home Assistant API uses functions called processors. These functions take a Response object as a parameter and return the python data type associated with the content-type header. How To Register Response Processors (Converters) @@ -34,7 +38,7 @@ To register a response processor you need to import the Processing class and the print(client.get_entities()) -In this example. +In this example. The first processor (a function wrapped with the processor decorator) is going to be called when we receive a response that has that as its :code:`Content-Type` header. Because :code:`homeassistant_api` provides processors for :code:`application/octet-stream` and :code:`application/json` by default, we need to tell :code:`homeassistant_api` to override the default processor with :code:`override=True`. diff --git a/docs/api.rst b/docs/api.rst index 4c503cdb..b97bd5c7 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -11,10 +11,6 @@ Clients :members: :inherited-members: -.. autoclass:: homeassistant_api._async.AsyncClient - :members: - :inherited-members: - Data Models ------------ @@ -38,19 +34,19 @@ Data Models :members: -.. autoclass:: homeassistant_api._async.AsyncDomain +.. autoclass:: AsyncDomain :members: -.. autoclass:: homeassistant_api._async.AsyncService +.. autoclass:: AsyncService :members: -.. autoclass:: homeassistant_api._async.AsyncGroup +.. autoclass:: AsyncGroup :members: -.. autoclass:: homeassistant_api._async.AsyncEntity +.. autoclass:: AsyncEntity :members: -.. autoclass:: homeassistant_api._async.AsyncEvent +.. autoclass:: AsyncEvent :members: diff --git a/docs/index.rst b/docs/index.rst index a88d8a1c..629c678f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,7 +9,8 @@ Welcome to Homeassistant API! ============================= -Homeassistant API is a pythonic module that interacts with `Homeassistant's REST API integration `_ +Homeassistant API is a pythonic module that interacts with `Homeassistant's REST API integration `_. +You can use it to remotely control your Home Assistant like getting entity states, triggering services, etc. Index ---------- @@ -26,11 +27,11 @@ Index Features ---------- -- Full consumption of the Homeassistant REST API endpoints -- Convenient Pydantic Models for data validation -- Syncrononous and Asynchronous support for integrating in with all applications and/or libraries -- Modular design for intuitive readability -- Request caching for efficiencient repeated requests. +- Full consumption of the Home Assistant REST API endpoints. +- Convenient Pydantic Models for data validation. +- Syncrononous and Asynchronous support for integrating with all applications and/or libraries. +- Modular design for intuitive readability. +- Request caching for more efficient repeative requests. Getting Started ------------------- @@ -43,9 +44,9 @@ Example .. literalinclude:: ../examples/basic.py :language: python -Want more? +Want to look at more? Many more examples are available in the :resource:`repository `. -Feel feel to open a pull request and add your own! +We encourage you to open a pull request and add your own after you get to know the library! See the :ref:`Contributing Section `. @@ -59,13 +60,14 @@ View the documentation for each class and method :doc:`here `. Contributing Guidelines -------------------------- -We very warmly welcome contributions. -This library has come a long way since its one-file humble beginning. On a Saturday afternoon with some buddies. -But while miuch has been done already there is still much much much more to do! Which is exciting! -If you're a developer that has an idea, suggestion or just wants to be helpful because you're nice. -See our *newly minted* :ref:`Development page ` for contribution ideas, guidelines, procedures and what to expect with your PR. -Happy developing! We hope to see your PRs soon. - +We absolutely looooooooooove contributions! +This library has come a long way since its one-file humble beginning, on a Saturday afternoon with some our programming buddies. +But while much has been done already there is still much much much more to do! +Which is exciting! +If you're a developer that has an idea, suggestion or just wants to be helpful because you're an awesome person. +See our \*newly minted\* :ref:`Development and Contribution page ` for contribution ideas, guidelines, procedures and what to expect with your PR. +Happy developing! +We hope to see your PRs soon. .. We would love to give a special shoutout to `FoxNerdSaysMoo ` for contributions to some of the awesome theme styling on these docs! diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 742bb186..31966f8b 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -9,29 +9,29 @@ Prerequisites Homeassistant --------------- -Before using this library, you need to have Homeassistant OS running on a device. -Something like a `Raspberry Pi 3 or 4 ` or spare laptop. -If you don't want to do that you can setup a Homeassistant container on your laptop or desktop with docker. -See `here `__ for how to install the installation right for you. +Before using this library, you need to have Home Assistant OS running on a device. +Something like a `Raspberry Pi 3 or 4 `_ or spare laptop. +If you don't want to do that you can setup a Home Assistant container on your laptop or desktop with docker. +See `here `_ for how to install the installation right for you. Configuring the REST API Server in Homeassistant ======================================================= Enable the :code:`api` integration in Homeassistant ------------------------------------------------------ -This library requires that the :code:`api` integration on your Homeassistant is enabled. +This library requires that the :code:`api` integration on your Home Assistant is enabled. It is enabled by default with the :code:`default_config` integration. But if by chance you have disabled :code:`default_config` you need to enable :code:`api`. Which requires the :code:`http` integration as well. -(Again most likely alreayd enabled on most installations.) +(Again most likely already enabled on most installations of Home Assistant.) If you are not sure if it is enabled or not, chances are if your frontend is enabled, so is your API Server. Access Token -------------- -Then once you have done that you need to head over to your profile and set up a "Long Lived Access Token" to input to your script later. -A good guide on how to do that is `here `__ +Then once you have done that you need to head over to your profile and set up a "Long Lived Access Token" to use in your code later. +A good guide on how to do that is `here `_ -Exposing Homeassistant to the Web +Exposing Home Assistant to the Web -------------------------------------- You may want to setup remote access through a Dynamic DNS server like DuckDNS (a good youtube tutorial on how to do that `here `_, keep in mind you will need to port forward to set that up.) If you do pursue this your api url will be something like :code:`https://yourhomeassistant.duckdns.org:8123/api`. @@ -48,14 +48,11 @@ Installation with pip is really easy and will install the dependencies this proj .. code-block:: bash - # To install the latest stable version from Pypi + # To install the latest stable version from PyPI $ pip install homeassistant_api - # To install the latest dev version - $ pip install git+https://github.com/GrandMoff100/HomeassistantAPI - - # Example of installing a pre-release - $ pip install homeassistant_api==2.0.0a1 + # To install the latest dev version (you'll need to use poetry because pip, by itself, does not understand poetry dependencies.) + $ poetry add git+https://github.com/GrandMoff100/HomeassistantAPI Installing with :code:`git` @@ -84,7 +81,7 @@ Then you should be all set to start using the project! If run into any problems Example Usages ================ Some examples applications of this project include integrating it into a another library, flask application or just a regular python script. -Maybe you want to start a project that allows you to use your homeassistant from your command line but some sassy responses. -Or maybe add it to a discord bot to manage your homeassistant from inside discord (you might want to use AsyncClient if you do, *hint hint wink wink nudge nudge*) -In any event, the possibilities are endless, so go make some cool stuff and share it with us on the :resource:`repository `! +Maybe you want to start a project that allows you to use your Home Assistant from your command line but some sassy responses. +Or maybe add it to a discord bot to manage your Home Assistant from inside discord. +In any event, the possibilities are endless, so go make some cool stuff and share it with us on the :resource:`repository `! We hope to see your project soon! diff --git a/docs/usage.rst b/docs/usage.rst index 582c26e2..e7ad5c7a 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -7,10 +7,9 @@ The Basics... ************** This library is centered around the :code:`Client` class. -Once you have have your api base url and Long Lived Access Token from homeassistant we can start to do stuff. -The rest of this guide assumes you have the :code:`Client` (or :code:`AsyncClient`) saved to a :code:`client` variable like this. -Most of these examples require some integrations to be setup inside homeassistant for the examples to actually work. - +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 :code:`Client` saved to a :code:`client` variable. +Most of these examples require some integrations to be setup inside Home Assistant for the examples to actually work. .. code-block:: python :linenos: @@ -21,8 +20,27 @@ Most of these examples require some integrations to be setup inside homeassistan URL = '' TOKEN = '' + # Assigns the Client object to a variable and checks if it's running. client = Client(URL, TOKEN) + service = client.get_domain("light") # Gets the light service domain from Home Assistant + + service.turn_on(entity_id="light.my_living_room_light") + # Triggers the light.turn_on service on the entity `light.my_living_room_light` + + +.. code-block:: python + :linenos: + + from homeassistant_api import Client + + # You can also initialize Client before you use it. + + client = Client("https://myhomeassistant.duckdns.org:8123/api", "mylongtokenpasswordthingyfoobar") + + with client: + client.get_state("") + Client ======== @@ -32,17 +50,12 @@ Services --------- .. code-block:: python - domains = client.get_domains() - # {'homeassistant': , 'notify': } + light = client.get_domain("light") - service = domains.cover.services.close_cover # Works the same as domains['cover'].services['open_cover'] - # - - changed_states = client.trigger_service('cover', 'close_cover', entity_id='cover.garage_door') - # Alternatively (using fetched service from above) - changed_states = service.trigger(entity_id='cover.garage_door') - # [] + print(light.services) + # {'turn_on': Service(service_id='turn_on', name='Turn on', description='Turn on one or more lights and adjust properties of the light, even when they are turned on already.\n', ... + changed_states = light.toggle(entity_id="light.light_bulb_1") Entities --------- @@ -50,56 +63,55 @@ Entities .. code-block:: python entity_groups = client.get_entities() - # {'person': , 'zone': , ...} + # {'person': , 'zone': , ...} door = client.get_entity(entity_id='cover.garage_door') # "> states = client.get_states() - # [, ,...] + # [, ,...] state = client.get_state('sun.sun') - # + # new_state = client.set_state(state='my ToaTallY Whatever vAlUe 12t87932', group_id='my_favorite_colors', entity_slug='number_one') - # + # # Alternatively you can set state from the entity class itself from homeassistant_api import State # If you are wondering where door came from its about 15 lines up. - door.state.state = 'My new state' - door.state.attributes['open_height'] = '23' - door.set_state(door.state) - # - + door.set_state(State(state="My new state", attributes={"open_height": "5ft"})) + # -AsyncClient -============= -Before you run this code you need to install the :code:`homeassistant_api[async]` (it just installs :code:`aiohttp`). -Here is the async counterpart to the usage above. -Except how to run async code in the console without starting an eventloop yourself you ask? You can install :code:`aioconsole` and then run :code:`$ apython` +Using Client with `async`/`await` +==================================== +Are you wondering if you can use `homeassistant_api` using Python's `async`/`await` syntax? +Good news! You can! Services ------------ .. code-block:: python - from homeassistant_api._async import AsyncClient + import asyncio + from homeassistant_api import Client + + # Initialize client like usual + client = Client(url, token) - client = AsyncClient(url, token) + async def main(): - domains = await client.get_domains() - # {'homeassistant': , 'notify': } + domains = await client.async_get_domains() + print(domains) + # {'homeassistant': , 'notify': } - service = domains.cover.services.close_cover # Works the same as domains['cover'].services['open_cover'] - # + cover = await client.async_get_domain("cover") - changed_states = client.trigger_service('cover', 'close_cover', entity_id='cover.garage_door') - # Alternatively (using fetched service from above) - changed_states = service.trigger(entity_id='cover.garage_door') - # [] + changed_states = cover.close_cover(entity_id='cover.garage_door') + # [] + asyncio.get_event_loop().run_until_complete(main()) Entities ----------- @@ -140,5 +152,5 @@ Browse below to learn more about what you can do with :code:`homeassistant_api`. .. toctree:: :maxdepth: 2 - processing - api \ No newline at end of file + api + advanced diff --git a/examples/basic.py b/examples/basic.py index f60efc1e..19eca9f0 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,14 +1,18 @@ import os -from homeassistant_api import Client +from homeassistant_api import Client api_url = "https://larsen-hassio.duckdns.org:8123/api" # Something like http://localhost:8123/api -token = os.getenv("HOMEASSISTANT_TOKEN") # Used to aunthenticate yourself with homeassistant +token = os.getenv( + "HOMEASSISTANT_TOKEN" +) # Used to aunthenticate yourself with homeassistant # See the documentation on how to obtain a Long Lived Access Token -with Client(api_url, token) as client: # Create Client objecty and check that its running. - light = client.get_domain("light") +with Client( + api_url, token +) as client: # Create Client object and check that its running. + cover = client.get_domain("cover") - # Tells homeassistant to trigger the turn_on service on the given entity_id - light.turn_on(entity_id="light.front_room") + # Tells Home Assistant to trigger the toggle service on the given entity_id + cover.toggle(entity_id="cover.garage_door") diff --git a/examples/myq_grarage_door.py b/examples/myq_grarage_door.py index 584a72e8..cc7e5f12 100644 --- a/examples/myq_grarage_door.py +++ b/examples/myq_grarage_door.py @@ -5,13 +5,13 @@ api_url = os.getenv("API_URL") token = os.getenv("TOKEN") + if api_url is not None and token is not None: # Intitializes the main Client client = Client(api_url, token) # Verifies the extistence of the specified server and opens efficient ClientSessions. with client: # Gets the cover service domain - cover = client.get_domain("cover") - + light = client.get_domain("light") # Triggers the service with a specific garage door - cover.open_garage(entity_id="cover.my_garage_door") + print(light.toggle(entity_id="light.light_bulb_1")) diff --git a/homeassistant_api/__init__.py b/homeassistant_api/__init__.py index c0b7780b..25733fc6 100644 --- a/homeassistant_api/__init__.py +++ b/homeassistant_api/__init__.py @@ -1,4 +1,5 @@ """Imports all library stuff for convenience.""" +from ._async import AsyncDomain, AsyncEntity, AsyncEvent, AsyncGroup, AsyncService from .client import Client from .errors import ( APIConfigurationError, @@ -14,3 +15,11 @@ ) from .models import Domain, Entity, Event, Group, History, Service, State from .processing import Processing + +Domain.update_forward_refs(**locals()) +Entity.update_forward_refs(**locals()) +Event.update_forward_refs(**locals()) +Group.update_forward_refs(**locals()) +History.update_forward_refs(**locals()) +Service.update_forward_refs(**locals()) +State.update_forward_refs(**locals()) diff --git a/homeassistant_api/_async/asyncclient.py b/homeassistant_api/_async/asyncclient.py index 0b7c0971..529d562e 100644 --- a/homeassistant_api/_async/asyncclient.py +++ b/homeassistant_api/_async/asyncclient.py @@ -1,9 +1,10 @@ -"""Module for interacting with homeassistant asyncronously.""" +"""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 @@ -24,16 +25,16 @@ class RawAsyncClient(RawWrapper, JsonProcessingMixin): :param global_request_kwargs: A dictionary or dict-like object of kwargs to pass to :func:`requests.request` or :meth:`aiohttp.ClientSession.request`. Optional. """ # pylint: disable=line-too-long - _session: Optional[CachedSession] = None + _async_session: Optional[CachedSession] = None async def __aenter__(self): - self._session = CachedSession(expire_after=30) - await self._session.__aenter__() + self._async_session = CachedSession(expire_after=30) + await self._async_session.__aenter__() await self.async_check_api_running() return self async def __aexit__(self, cls, obj, traceback): - await self._session.__aexit__(cls, obj, traceback) + await self._async_session.__aexit__(cls, obj, traceback) # Very important request function async def async_request( @@ -47,18 +48,26 @@ async def async_request( try: if self.global_request_kwargs is not None: kwargs.update(self.global_request_kwargs) - assert self._session is not None - resp = await self._session.request( + if self._async_session is not None: + return await self.async_response_logic( + await self._async_session.request( + method, + self.endpoint(path), + headers=self.prepare_headers(headers), + **kwargs, + ) + ) + async with aiohttp.request( method, self.endpoint(path), headers=self.prepare_headers(headers), **kwargs, - ) + ) as resp: + return await self.async_response_logic(resp) except asyncio.exceptions.TimeoutError as err: raise RequestError( - f'Homeassistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)' + f'Home Assistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)' ) from err - return await self.async_response_logic(resp) @staticmethod async def async_response_logic(response): @@ -68,7 +77,7 @@ async def async_response_logic(response): # API information methods async def async_api_error_log(self) -> str: """Returns the server error log as a string""" - return cast(str, await self.async_request("error_log", return_text=True)) + return cast(str, await self.async_request("error_log")) async def async_api_config(self) -> Dict[str, Any]: """Returns the yaml configuration of homeassistant""" @@ -116,7 +125,7 @@ async def async_get_entity_histories( yield History.parse_obj({"states": states}) async def async_get_rendered_template(self, template: str): - """Renders a given Jinja2 template string with homeassistant context data.""" + """Renders a given Jinja2 template string with Home Assistant context data.""" return await self.async_request( "template", json=dict(template=template), @@ -130,7 +139,7 @@ async def async_get_discovery_info(self) -> Dict[str, Any]: # API check methods async def async_check_api_config(self) -> bool: - """Asks homeassistant to validate its configuration file""" + """Asks Home Assistant to validate its configuration file""" res = await self.async_request("config/core/check_config", method="POST") res = cast(Dict[Any, Any], res) valid = {"valid": True, "invalid": False}.get( @@ -145,7 +154,7 @@ async def async_check_api_config(self) -> bool: return valid async def async_check_api_running(self) -> bool: - """Asks homeassistant if its running""" + """Asks Home Assistant if its running""" res = cast(Dict[Any, Any], await self.async_request("")) if res.get("message", None) == "API running.": return True @@ -202,7 +211,7 @@ async def async_trigger_service( service: str, **service_data, ) -> List[State]: - """Tells homeassistant to trigger a service, returns stats changed while being called""" + """Tells Home Assistant to trigger a service, returns stats changed while being called""" data = await self.async_request( join("services", domain, service), method="POST", diff --git a/homeassistant_api/_async/models/entity.py b/homeassistant_api/_async/models/entity.py index c03e48f2..331a293c 100644 --- a/homeassistant_api/_async/models/entity.py +++ b/homeassistant_api/_async/models/entity.py @@ -2,9 +2,7 @@ from os.path import join from typing import TYPE_CHECKING, Any, Dict, Optional, cast -from pydantic import BaseModel - -from ...models import History, State +from ...models import BaseModel, History, State if TYPE_CHECKING: from homeassistant_api import Client @@ -40,7 +38,7 @@ async def async_get_state(self) -> State: return self.state async def async_fetch_state(self) -> State: - """Asks homeassistant for the state of the entity and sets it locally""" + """Asks Home Assistant for the state of the entity and sets it locally""" state_data = await self.group.client.async_request( join("states", self.entity_id) ) @@ -50,7 +48,7 @@ async def async_fetch_state(self) -> State: return self.state async def async_set_state(self, state: State) -> State: - """Tells homeassistant to set the given State object.""" + """Tells Home Assistant to set the given State object.""" return await self.group.client.async_set_state( self.entity_id, group=self.group.group_id, diff --git a/homeassistant_api/_async/models/events.py b/homeassistant_api/_async/models/events.py index 9656b424..81e3aeed 100644 --- a/homeassistant_api/_async/models/events.py +++ b/homeassistant_api/_async/models/events.py @@ -1,7 +1,7 @@ """Event Model File""" from typing import TYPE_CHECKING, Dict, cast -from pydantic import BaseModel +from ...models import BaseModel if TYPE_CHECKING: from homeassistant_api import Client @@ -9,7 +9,7 @@ class AsyncEvent(BaseModel): """ - Event class for Homeassistant Event Triggers + Event class for Home Assistant Event Triggers For attribute information see the Data Science docs on Event models. https://data.home-assistant.io/docs/events diff --git a/homeassistant_api/client.py b/homeassistant_api/client.py index db9159ec..b40d8ef0 100644 --- a/homeassistant_api/client.py +++ b/homeassistant_api/client.py @@ -3,5 +3,5 @@ from .rawclient import RawClient -class Client(RawClient, RawAsyncClient): # type: ignore[misc] +class Client(RawClient, RawAsyncClient): """The all-in-one class to interact with Home Assistant!""" diff --git a/homeassistant_api/errors.py b/homeassistant_api/errors.py index 5f27b262..3682ffc3 100644 --- a/homeassistant_api/errors.py +++ b/homeassistant_api/errors.py @@ -29,11 +29,11 @@ class ParameterMissingError(HomeassistantAPIError): class UnexpectedStatusCodeError(HomeassistantAPIError): - """Error raised when Homeassistant returns a response with status code that was unexpected.""" + """Error raised when Home Assistant returns a response with status code that was unexpected.""" def __init__(self, code: int, content): super().__init__( - f"Homeassistant return response with an unrecognized status code {code!r}.\n{content}" + f"Home Assistant return response with an unrecognized status code {code!r}.\n{content}" ) diff --git a/homeassistant_api/models/__init__.py b/homeassistant_api/models/__init__.py index f1164072..99a6b1c7 100644 --- a/homeassistant_api/models/__init__.py +++ b/homeassistant_api/models/__init__.py @@ -1,4 +1,5 @@ """Imports Model objects for convenience.""" +from .base import BaseModel from .domains import Domain, Service from .entity import Entity, Group from .events import Event diff --git a/homeassistant_api/models/base.py b/homeassistant_api/models/base.py new file mode 100644 index 00000000..bca15cbd --- /dev/null +++ b/homeassistant_api/models/base.py @@ -0,0 +1,14 @@ +"""Module for Global Base Model Configuration inheritance.""" + +from pydantic import BaseModel as PydanticBaseModel + + +class BaseModel(PydanticBaseModel): + """Base model that all Library Models inherit from.""" + + class Config: + """Pydantic config class for all library models.""" + + arbitrary_types_allowed = True + smart_union = True + validate_assignment = True diff --git a/homeassistant_api/models/domains.py b/homeassistant_api/models/domains.py index c280bff8..ef64b120 100644 --- a/homeassistant_api/models/domains.py +++ b/homeassistant_api/models/domains.py @@ -1,9 +1,7 @@ """File for Service and Domain data models""" +from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple -from typing import TYPE_CHECKING, Dict, Optional, Tuple - -from pydantic import BaseModel - +from .base import BaseModel from .states import State if TYPE_CHECKING: @@ -20,7 +18,13 @@ class Domain(BaseModel): def add_service(self, service_id: str, **data) -> None: """Registers services into a domain to be used or accessed""" self.services.update( - {service_id: Service(service_id=service_id, domain=self, **data)} + { + service_id: Service( + service_id=service_id, + domain=self, + **data, + ) + } ) def get_service(self, service_id: str): @@ -36,15 +40,25 @@ def __getattr__(self, attr: str): return super().__getattribute__(attr) +class ServiceField(BaseModel): + """Model for service parameters/fields.""" + + description: str + example: Any + selector: Optional[Dict[str, Any]] = None + name: Optional[str] = None + required: Optional[bool] = None + + class Service(BaseModel): """Model representing services from homeassistant""" service_id: str domain: Domain name: Optional[str] = None - description: Optional[Dict[str, str]] = None - fields: Optional[Dict[str, str]] = None - target: Optional[Dict[str, str]] = None + description: Optional[str] = None + fields: Optional[Dict[str, ServiceField]] = None + target: Optional[Dict[str, dict]] = None def trigger(self, **service_data) -> Tuple[State, ...]: """Triggers the service associated with this object.""" diff --git a/homeassistant_api/models/entity.py b/homeassistant_api/models/entity.py index d3ee0c14..53ed393a 100644 --- a/homeassistant_api/models/entity.py +++ b/homeassistant_api/models/entity.py @@ -3,8 +3,7 @@ from os.path import join from typing import TYPE_CHECKING, Any, Dict, Optional, cast -from pydantic import BaseModel - +from .base import BaseModel from .history import History from .states import State @@ -51,7 +50,7 @@ def get_state(self) -> State: return self.state def fetch_state(self) -> State: - """Asks homeassistant for the state of the entity and sets it locally""" + """Asks Home Assistant for the state of the entity and sets it locally""" state_data = self.group.client.request(join("states", self.entity_id)) self.state = self.group.client.process_state_json( cast(Dict[str, Any], state_data) @@ -60,7 +59,7 @@ def fetch_state(self) -> State: def set_state(self, state: State) -> State: """ - Tells homeassistant to set the given State object. + Tells Home Assistant to set the given State object. (You can construct the state object yourself.) """ state_data = self.group.client.request( diff --git a/homeassistant_api/models/events.py b/homeassistant_api/models/events.py index 3f5c3342..67f1a590 100644 --- a/homeassistant_api/models/events.py +++ b/homeassistant_api/models/events.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Dict, cast -from pydantic import BaseModel +from .base import BaseModel if TYPE_CHECKING: from homeassistant_api import Client @@ -10,7 +10,7 @@ class Event(BaseModel): """ - Event class for Homeassistant Event Triggers + Event class for Home Assistant Event Triggers For attribute information see the Data Science docs on Event models https://data.home-assistant.io/docs/events diff --git a/homeassistant_api/models/history.py b/homeassistant_api/models/history.py index 762e8a04..5add5406 100644 --- a/homeassistant_api/models/history.py +++ b/homeassistant_api/models/history.py @@ -1,8 +1,7 @@ """Module for the History model.""" from typing import Tuple -from pydantic import BaseModel - +from .base import BaseModel from .states import State diff --git a/homeassistant_api/models/states.py b/homeassistant_api/models/states.py index 2b89697c..a502981c 100644 --- a/homeassistant_api/models/states.py +++ b/homeassistant_api/models/states.py @@ -2,7 +2,7 @@ from datetime import datetime from typing import Any, Dict, Optional -from pydantic import BaseModel +from .base import BaseModel class State(BaseModel): @@ -13,4 +13,4 @@ class State(BaseModel): attributes: Dict[str, Any] = {} last_changed: Optional[datetime] = None last_updated: Optional[datetime] = None - context: Dict[str, str] = {} + context: Dict[str, Optional[str]] = {} diff --git a/homeassistant_api/processing.py b/homeassistant_api/processing.py index 043b4655..5ed71c3a 100644 --- a/homeassistant_api/processing.py +++ b/homeassistant_api/processing.py @@ -7,7 +7,6 @@ import simplejson from aiohttp import ClientResponse from aiohttp_client_cache.response import CachedResponse as AsyncCachedResponse -from pydantic import BaseModel from requests import Response from requests_cache import CachedResponse @@ -20,6 +19,7 @@ UnauthorizedError, UnexpectedStatusCodeError, ) +from .models import BaseModel class Processing(BaseModel): @@ -28,11 +28,6 @@ class Processing(BaseModel): response: Union[Response, CachedResponse, ClientResponse, AsyncCachedResponse] _processors: Dict[str, Tuple[Callable, ...]] = {} - class Config: # pylint: disable=too-few-public-methods - """A pydantic config class.""" - - arbitrary_types_allowed: bool = True - @staticmethod def processor(mimetype: str): """A decorator used to register a response converter function.""" @@ -101,7 +96,7 @@ def process_json(response): return response.json() except (json.decoder.JSONDecodeError, simplejson.decoder.JSONDecodeError) as err: raise MalformedDataError( - f"Homeassistant responded with non-json response: {repr(response.text)}" + f"Home Assistant responded with non-json response: {repr(response.text)}" ) from err @@ -118,7 +113,7 @@ async def async_process_json(response): return await response.json() except (json.decoder.JSONDecodeError, simplejson.decoder.JSONDecodeError) as err: raise MalformedDataError( - f"Homeassistant responded with non-json response: {repr(await response.text())}" + f"Home Assistant responded with non-json response: {repr(await response.text())}" ) from err diff --git a/homeassistant_api/rawclient.py b/homeassistant_api/rawclient.py index 942dfc3f..a39360f8 100644 --- a/homeassistant_api/rawclient.py +++ b/homeassistant_api/rawclient.py @@ -48,16 +48,23 @@ def request( try: if self.global_request_kwargs is not None: kwargs.update(self.global_request_kwargs) - assert self._session is not None - resp = self._session.request( - method, - self.endpoint(path), - headers=self.prepare_headers(headers), - **kwargs, - ) + if self._session is not None: + resp = self._session.request( + method, + self.endpoint(path), + headers=self.prepare_headers(headers), + **kwargs, + ) + else: + resp = requests.request( + method, + self.endpoint(path), + headers=self.prepare_headers(headers), + **kwargs, + ) except requests.exceptions.Timeout as err: raise RequestError( - f'Homeassistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)' + f'Home Assistant did not respond in time (timeout: {kwargs.get("timeout", 300)} sec)' ) from err return self.response_logic(resp) @@ -113,7 +120,7 @@ def get_entity_histories(self, *args, **kwargs) -> Generator[History, None, None def get_rendered_template(self, template: str) -> str: """ - Renders a Jinja2 template with homeassistant context data. + Renders a Jinja2 template with Home Assistant context data. See https://developers.home-assistant.io/docs/api/rest/. """ return cast( @@ -133,7 +140,7 @@ def get_discovery_info(self) -> Dict[str, Any]: # API check methods def check_api_config(self) -> bool: - """Asks homeassistant to validate its configuration file""" + """Asks Home Assistant to validate its configuration file""" res = cast(dict, self.request("config/core/check_config", method="POST")) valid = {"valid": True, "invalid": False}.get(res["result"], False) if valid is False: @@ -141,7 +148,7 @@ def check_api_config(self) -> bool: return valid def check_api_running(self) -> bool: - """Asks homeassistant if its running""" + """Asks Home Assistant if its running""" res = self.request("") if cast(dict, res).get("message", None) == "API running.": return True @@ -192,8 +199,13 @@ def get_domains(self) -> Tuple[Domain, ...]: ) return tuple(services) - def get_domain(self, domain: str) -> Domain: + def get_domain(self, domain_id: str) -> Optional[Domain]: """Fetchers all services under a particular domain.""" + domains = self.get_domains() + for domain in domains: + if domain.domain_id == domain_id: + return domain + return None def trigger_service( self, @@ -201,7 +213,7 @@ def trigger_service( service: str, **service_data, ) -> Tuple[State, ...]: - """Tells homeassistant to trigger a service, returns stats changed while being called""" + """Tells Home Assistant to trigger a service, returns stats changed while being called""" data = self.request( join("services", domain, service), method="POST", diff --git a/pyproject.toml b/pyproject.toml index 82714888..e0a33c45 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,7 +44,8 @@ profile = "black" disable = [ "invalid-name", "duplicate-code", - "no-member" + "no-member", + "too-few-public-methods" ] [tool.pylint.master] From 5217af2a9b23223ca250ec410325a4a7b2ea2247 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Mon, 21 Feb 2022 04:27:24 +0000 Subject: [PATCH 07/11] Added cache expiration and cache backend arguments --- docs/advanced.rst | 21 ++++++---- docs/usage.rst | 47 +++++++++++++---------- examples/async_get_entities.py | 2 +- examples/basic.py | 4 +- examples/myq_grarage_door.py | 1 + homeassistant_api/_async/models/entity.py | 4 -- homeassistant_api/models/entity.py | 6 +-- homeassistant_api/processing.py | 4 +- homeassistant_api/rawapi.py | 15 ++++++-- homeassistant_api/rawclient.py | 5 ++- pyproject.toml | 4 +- 11 files changed, 68 insertions(+), 45 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index aa664347..2570f18b 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -2,6 +2,10 @@ Advanced Section ******************* +Persistent Caching +******************** + + Response Processing ********************** Home Assistant API uses functions called processors. @@ -19,16 +23,16 @@ To register a response processor you need to import the Processing class and the from homeassistant_api.processing import process_json - @Processing.processor("application/octet-stream", override=True) + @Processing.processor("application/octet-stream") def text_processor(response): return response.text.lower() - @Processing.async_processor("text/csv") + @Processing.processor("text/csv") async def async_text_processor(response): text = await response.text() return [line.split(",") for line in text.splitlines()] - @Processing.processor("application/json", override=True) + @Processing.processor("application/json") def json_processor(response): print("I processed a json response!) return process_json(response) @@ -40,12 +44,13 @@ To register a response processor you need to import the Processing class and the In this example. The first processor (a function wrapped with the processor decorator) is going to be called when we receive a response that has that as its :code:`Content-Type` header. -Because :code:`homeassistant_api` provides processors for :code:`application/octet-stream` and :code:`application/json` by default, -we need to tell :code:`homeassistant_api` to override the default processor with :code:`override=True`. +:code:`homeassistant_api` provides processors for :code:`application/octet-stream` and :code:`application/json` by default, +But :code:`@Processing.processor` gives the most recently registered processor the highest precedence when choosing a processor for a response. +So our processor here will be chosen over the default processors. -The second processor is an async processor that only gets called when AsyncClient receives a response that has :code:`text/csv` as its :code:`Content-Type` header. -If you wanted to override :code:`homeassistant_api`'s default json processing using the :code:`json` module with a different way to process json data. -Such as using instead, the :code:`ujson` module (which is faster but more limiting). +The second processor is an async processor that only gets called when Client receives an async response that has :code:`text/csv` as its :code:`Content-Type` header. +If you wanted, you could not use :code:`homeassistant_api`'s default json processing using the :code:`json` module, +and use instead the :code:`ujson` module (which is faster but more restrictive). The third processor function implements the default processor function for the :code:`application/json` mimetype after printing a string. If you wanted to run some intermediate processing. diff --git a/docs/usage.rst b/docs/usage.rst index e7ad5c7a..93bb6495 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -4,12 +4,14 @@ Usage The Basics... -************** +################# This library is centered around the :code:`Client` class. 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 :code:`Client` saved to a :code:`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: @@ -32,22 +34,27 @@ Most of these examples require some integrations to be setup inside Home Assista .. code-block:: python :linenos: + from datetime import datetime from homeassistant_api import Client # You can also initialize Client before you use it. client = Client("https://myhomeassistant.duckdns.org:8123/api", "mylongtokenpasswordthingyfoobar") + # If you want to use caching you can use client as a context manager like so with client: - client.get_state("") - - -Client -======== -The most commonly used features of this library include triggering services and getting and modifying entity states. + while True: + sun = client.get_entity(entity_id="sun.sun") + state = sun.get_state() # Because requests are cached we reduce bandwidth usage :D + # Cache expires every 30 seconds by default. + if state.state == "below_horizon": + difference = datetime.now() - state.last_updated + print("Sun set", difference.seconds, "seconds ago.") + break Services ---------- +********** + .. code-block:: python light = client.get_domain("light") @@ -58,7 +65,7 @@ Services changed_states = light.toggle(entity_id="light.light_bulb_1") Entities ---------- +************* .. code-block:: python @@ -86,12 +93,12 @@ Entities Using Client with `async`/`await` -==================================== +************************************ Are you wondering if you can use `homeassistant_api` using Python's `async`/`await` syntax? Good news! You can! Services ------------- +************ .. code-block:: python import asyncio @@ -108,29 +115,29 @@ Services cover = await client.async_get_domain("cover") - changed_states = cover.close_cover(entity_id='cover.garage_door') + changed_states = await cover.close_cover(entity_id='cover.garage_door') # [] asyncio.get_event_loop().run_until_complete(main()) Entities ------------ +********* .. code-block:: python - entity_groups = await client.get_entities() + entity_groups = await client.async_get_entities() # {'person': , 'zone': , ...} - door = await client.get_entity(entity_id='cover.garage_door') + door = await client.async_get_entity(entity_id='cover.garage_door') # "> - states = await client.get_states() + states = await client.async_get_states() # [, ,...] - state = await client.get_state('sun.sun') + state = await client.async_get_state('sun.sun') # - new_state = await client.set_state(state='my ToaTallY Whatever vAlUe 12t87932', group_id='my_favorite_colors', entity_slug='number_one') + new_state = await client.async_set_state(state='my ToaTallY Whatever vAlUe 12t87932', group_id='my_favorite_colors', entity_slug='number_one') # # Alternatively you can set state from the entity class itself @@ -139,12 +146,12 @@ Entities # If you are wondering where door came from its about 15 lines up. door.state.state = 'My new state' door.state.attributes['open_height'] = '23' - await door.set_state(door.state) + await door.async_set_state(door.state) # What's Next? -************* +############# Browse below to learn more about what you can do with :code:`homeassistant_api`. diff --git a/examples/async_get_entities.py b/examples/async_get_entities.py index 86071b43..c9bf0b89 100644 --- a/examples/async_get_entities.py +++ b/examples/async_get_entities.py @@ -10,7 +10,7 @@ async def main(): # Initialize main object client = Client(url, token) - # Uses async context manager to ping the server. + # Uses async context manager to ping the server and initialize caching. async with client: # All async methods are prefixed with `async_`. data = await client.async_get_entities() diff --git a/examples/basic.py b/examples/basic.py index 19eca9f0..9a985bef 100644 --- a/examples/basic.py +++ b/examples/basic.py @@ -8,9 +8,11 @@ ) # Used to aunthenticate yourself with homeassistant # See the documentation on how to obtain a Long Lived Access Token +assert token is not None with Client( - api_url, token + api_url, + token, ) as client: # Create Client object and check that its running. cover = client.get_domain("cover") diff --git a/examples/myq_grarage_door.py b/examples/myq_grarage_door.py index cc7e5f12..8ae9f0ec 100644 --- a/examples/myq_grarage_door.py +++ b/examples/myq_grarage_door.py @@ -13,5 +13,6 @@ with client: # Gets the cover service domain light = client.get_domain("light") + assert light is not None # Triggers the service with a specific garage door print(light.toggle(entity_id="light.light_bulb_1")) diff --git a/homeassistant_api/_async/models/entity.py b/homeassistant_api/_async/models/entity.py index 331a293c..f5db913d 100644 --- a/homeassistant_api/_async/models/entity.py +++ b/homeassistant_api/_async/models/entity.py @@ -34,10 +34,6 @@ class AsyncEntity(BaseModel): group: AsyncGroup async def async_get_state(self) -> State: - """Returns the state last fetched from the api.""" - return self.state - - async def async_fetch_state(self) -> State: """Asks Home Assistant for the state of the entity and sets it locally""" state_data = await self.group.client.async_request( join("states", self.entity_id) diff --git a/homeassistant_api/models/entity.py b/homeassistant_api/models/entity.py index 53ed393a..a1c85748 100644 --- a/homeassistant_api/models/entity.py +++ b/homeassistant_api/models/entity.py @@ -46,11 +46,7 @@ class Entity(BaseModel): group: Group def get_state(self) -> State: - """Returns the state last fetched from the api.""" - return self.state - - def fetch_state(self) -> State: - """Asks Home Assistant for the state of the entity and sets it locally""" + """Asks Home Assistant for the state of the entity and caches it locally""" state_data = self.group.client.request(join("states", self.entity_id)) self.state = self.group.client.process_state_json( cast(Dict[str, Any], state_data) diff --git a/homeassistant_api/processing.py b/homeassistant_api/processing.py index 5ed71c3a..6683c971 100644 --- a/homeassistant_api/processing.py +++ b/homeassistant_api/processing.py @@ -35,7 +35,9 @@ def processor(mimetype: str): def register_processor(processor): if mimetype not in Processing._processors: Processing._processors[mimetype] = tuple() - Processing._processors[mimetype] += (processor,) + Processing._processors[mimetype] = (processor,) + Processing._processors[ + mimetype + ] return processor return register_processor diff --git a/homeassistant_api/rawapi.py b/homeassistant_api/rawapi.py index 795c00cf..42e82be3 100644 --- a/homeassistant_api/rawapi.py +++ b/homeassistant_api/rawapi.py @@ -21,14 +21,23 @@ def __init__( api_url: str, token: str, global_request_kwargs: Optional[Dict[str, str]] = None, + cache_backend=None, + cache_expire_after: Optional[int] = None, ) -> None: - self.api_url = api_url - self.token = token if global_request_kwargs is None: global_request_kwargs = {} - self.global_request_kwargs = 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: + cache_expire_after = 30 + + self.api_url = api_url + self.token = token + self.global_request_kwargs = global_request_kwargs + self.cache_backend = cache_backend + self.cache_expire_after = cache_expire_after def endpoint(self, path: str) -> str: """Joins the api base url with a local path to an absolute url""" diff --git a/homeassistant_api/rawclient.py b/homeassistant_api/rawclient.py index a39360f8..cbcefca6 100644 --- a/homeassistant_api/rawclient.py +++ b/homeassistant_api/rawclient.py @@ -27,7 +27,10 @@ class RawClient(RawWrapper, JsonProcessingMixin): _session: Optional[CachedSession] = None def __enter__(self): - self._session = CachedSession(expire_after=30, backend="memory") + self._session = CachedSession( + expire_after=self.cache_expire_after, + backend=self.cache_backend, + ) self._session.__enter__() self.check_api_running() self.check_api_config() diff --git a/pyproject.toml b/pyproject.toml index e0a33c45..e59cd61b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,11 +45,13 @@ disable = [ "invalid-name", "duplicate-code", "no-member", - "too-few-public-methods" + "too-few-public-methods", + "too-many-arguments" ] [tool.pylint.master] extension-pkg-whitelist = ["pydantic"] +ignore-paths = ["examples"] [tool.bandit] skips = ["B105"] From e97ac8ebebc6274235d163e7270bc6752f52a25c Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Tue, 22 Feb 2022 00:18:14 +0000 Subject: [PATCH 08/11] Half-finished the developer docs. --- docs/api.rst | 66 ++++++------------ docs/conf.py | 1 + docs/dev_docs/contribution.rst | 2 - docs/dev_docs/currently.rst | 12 ---- docs/dev_docs/guidelines.rst | 3 - docs/dev_docs/procedures.rst | 3 - docs/dev_docs/tipsandtricks.rst | 3 - docs/development.rst | 76 ++++++++++++++++++--- docs/quickstart.rst | 4 +- docs/usage.rst | 6 +- homeassistant_api/_async/models/__init__.py | 2 +- homeassistant_api/models/__init__.py | 2 +- homeassistant_api/models/history.py | 4 +- 13 files changed, 100 insertions(+), 84 deletions(-) delete mode 100644 docs/dev_docs/contribution.rst delete mode 100644 docs/dev_docs/currently.rst delete mode 100644 docs/dev_docs/guidelines.rst delete mode 100644 docs/dev_docs/procedures.rst delete mode 100644 docs/dev_docs/tipsandtricks.rst diff --git a/docs/api.rst b/docs/api.rst index b97bd5c7..8283b6c0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,13 +1,10 @@ Code Reference *************** -.. currentmodule:: homeassistant_api - - Clients -------- -.. autoclass:: Client +.. autoclass:: homeassistant_api.Client :members: :inherited-members: @@ -15,67 +12,48 @@ Clients Data Models ------------ -.. autoclass:: Domain - :members: +.. automodule:: homeassistant_api.models -.. autoclass:: Service - :members: + .. autoclass:: Domain -.. autoclass:: Group - :members: + .. autoclass:: Service -.. autoclass:: Entity - :members: + .. autoclass:: Group -.. autoclass:: State - :members: + .. autoclass:: Entity -.. autoclass:: Event - :members: + .. autoclass:: History + .. autoclass:: State -.. autoclass:: AsyncDomain - :members: + .. autoclass:: Event -.. autoclass:: AsyncService - :members: -.. autoclass:: AsyncGroup - :members: -.. autoclass:: AsyncEntity - :members: +.. automodule:: homeassistant_api._async.models -.. autoclass:: AsyncEvent - :members: + .. autoclass:: AsyncDomain + .. autoclass:: AsyncService -Processing ------------ + .. autoclass:: AsyncGroup + .. autoclass:: AsyncEntity -.. autoclass:: Processing - :members: + .. autoclass:: AsyncEvent -Exceptions +Processing ----------- -.. autoclass:: homeassistant_api.errors.RequestError - -.. autoclass:: homeassistant_api.errors.MalformedDataError - -.. autoclass:: homeassistant_api.errors.MalformedInputError -.. autoclass:: homeassistant_api.errors.APIConfigurationError - -.. autoclass:: homeassistant_api.errors.ParameterMissingError - -.. autoclass:: homeassistant_api.errors.UnexpectedStatusCodeError +.. autoclass:: homeassistant_api.processing.Processing + :members: -.. autoclass:: homeassistant_api.errors.UnauthorizedError -.. autoclass:: homeassistant_api.errors.EndpointNotFoundError +Exceptions +----------- -.. autoclass:: homeassistant_api.errors.MethodNotAllowedError +.. automodule:: homeassistant_api.errors + :members: diff --git a/docs/conf.py b/docs/conf.py index edfff7d3..6d24d6f4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,6 +50,7 @@ "issues": "https://github.com/GrandMoff100/HomeassistantAPI/issues", "discussions": "https://github.com/GrandMoff100/HomeassistantAPI/discussions", "examples": f"https://github.com/GrandMoff100/HomeassistantAPI/tree/{branch}/examples", + "new_pr": "https://github.com/GrandMoff100/HomeAssistantAPI/compare", } # Add any paths that contain templates here, relative to this directory. diff --git a/docs/dev_docs/contribution.rst b/docs/dev_docs/contribution.rst deleted file mode 100644 index 53809eb5..00000000 --- a/docs/dev_docs/contribution.rst +++ /dev/null @@ -1,2 +0,0 @@ -Contribution Ideas -------------------- diff --git a/docs/dev_docs/currently.rst b/docs/dev_docs/currently.rst deleted file mode 100644 index c1f62142..00000000 --- a/docs/dev_docs/currently.rst +++ /dev/null @@ -1,12 +0,0 @@ -Current Project Status -======================= - - -TODOs -------------------- - -Roadmap -------------------- - -CHANGELOG -------------------- diff --git a/docs/dev_docs/guidelines.rst b/docs/dev_docs/guidelines.rst deleted file mode 100644 index 407b1d27..00000000 --- a/docs/dev_docs/guidelines.rst +++ /dev/null @@ -1,3 +0,0 @@ - -Guidelines -------------------- \ No newline at end of file diff --git a/docs/dev_docs/procedures.rst b/docs/dev_docs/procedures.rst deleted file mode 100644 index 83f14553..00000000 --- a/docs/dev_docs/procedures.rst +++ /dev/null @@ -1,3 +0,0 @@ - -Procedures -------------------- diff --git a/docs/dev_docs/tipsandtricks.rst b/docs/dev_docs/tipsandtricks.rst deleted file mode 100644 index 6bb4376a..00000000 --- a/docs/dev_docs/tipsandtricks.rst +++ /dev/null @@ -1,3 +0,0 @@ - -Tips and Tricks -------------------- diff --git a/docs/development.rst b/docs/development.rst index 40dac663..ea89ebaa 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -7,13 +7,71 @@ Development This page is where development related things are. See below. -.. toctree:: - :glob: - - dev_docs/contribution - dev_docs/guidelines - dev_docs/procedures - dev_docs/tipsandtricks - dev_docs/currently - +Contribution Ideas +********************* + +Setting up your Development Environment +***************************************** + +So now that you know what you want to contribute it is time to setup a development environment to make your changes in. + +Step One: Fork the Repository +=============================== + +Click `here `_ to fork the repostiory. The click your username. + +Step Two: Clone the Repository Locally +======================================= + +Next run in your terminal. + +.. code-block:: bash + + $ git clone https://github.com//HomeAssistantAPI + +Step Three: Installing Dependencies +====================================== + +Firstly, you need to have Python 3.7 or newer with Pip installed. Download the latest Python Version from `here `_. +Then you need to install the very popular Python Package Manager, :code:`poetry`. +Checkout the `Poetry docs `_. +You can install that with :code:`pip` by running :code:`pip install poetry`. +Now you can install the project's dependencies by running :code:`cd HomeAssistantAPI && poetry install` + +Step Four: [Optional] Setting Up a Home Assistant Development Environment. +============================================================================= + +If you do not have a Home Assistant installation running already, you can setup a Home Assistant Development environment. +Which is basically a local, unpackaged, Home Assistant Core installation, that runs with just Python (no Docker or Operating System). +You can start and stop the server really easily as it runs just in your terminal and gives you lots of control over it, making it ideal for testing your changes to Home Assistant API. +Follow this great guide `here `_ to do that. +After that you are now ready to make your changes to the codebase! + +Testing +******** +In order to test your changes you need to have an API URL, and a Long Lived Access Token. +Follow the :ref:`Quickstart Section ` for getting those. +If you setup the Development Environment then your API URL will most likely be something along the lines of :code:`https://localhost:8123/api`. +Then you can test your changes by passing the API URL, and Long Lived Access Token to the :code:`Client` object. + +.. _styling: + +Code Styling Guidelines +************************** + + +Catching Up With the Real World +********************************** + + +Merging Your Contributions +***************************** + +Once you have tested your changes and commited them to your fork you can merge them back into the :resource:`original repository `. +Head over to the :resource:`Pull Request Page ` and select your fork to merge into the `GrandMoff100/dev` branch. +Then you can hit "Create Pull Request" and we'll review it as soon as possible. +In order to be merged though, your code needs to follow our :ref:`Styling Guidelines `. +A Github Actions workflow will run on your PR automatically to verify that it does follow the guidelines. +Then once the checks have passed one of our maintainers will review the changes (basically to make sure your changes won't break anything ;)). +Then after that your changes will get merged and will be available in the next release! diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 31966f8b..4decb10a 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -9,7 +9,7 @@ Prerequisites Homeassistant --------------- -Before using this library, you need to have Home Assistant OS running on a device. +Before using this library, you need to have Home Assistant running on a device. Something like a `Raspberry Pi 3 or 4 `_ or spare laptop. If you don't want to do that you can setup a Home Assistant container on your laptop or desktop with docker. See `here `_ for how to install the installation right for you. @@ -26,6 +26,8 @@ Which requires the :code:`http` integration as well. (Again most likely already enabled on most installations of Home Assistant.) If you are not sure if it is enabled or not, chances are if your frontend is enabled, so is your API Server. +.. _access_token_setup: + Access Token -------------- Then once you have done that you need to head over to your profile and set up a "Long Lived Access Token" to use in your code later. diff --git a/docs/usage.rst b/docs/usage.rst index 93bb6495..ec4f6031 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -92,9 +92,9 @@ Entities # -Using Client with `async`/`await` -************************************ -Are you wondering if you can use `homeassistant_api` using Python's `async`/`await` syntax? +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! Services diff --git a/homeassistant_api/_async/models/__init__.py b/homeassistant_api/_async/models/__init__.py index 0a28ffb2..e15e7ef4 100644 --- a/homeassistant_api/_async/models/__init__.py +++ b/homeassistant_api/_async/models/__init__.py @@ -1,4 +1,4 @@ -"""Imports async Models for convenience.""" +"""The async Models for the entire library.""" from .domains import AsyncDomain, AsyncService from .entity import AsyncEntity, AsyncGroup from .events import AsyncEvent diff --git a/homeassistant_api/models/__init__.py b/homeassistant_api/models/__init__.py index 99a6b1c7..6d2a7934 100644 --- a/homeassistant_api/models/__init__.py +++ b/homeassistant_api/models/__init__.py @@ -1,4 +1,4 @@ -"""Imports Model objects for convenience.""" +"""The Model objects for the entire library.""" from .base import BaseModel from .domains import Domain, Service from .entity import Entity, Group diff --git a/homeassistant_api/models/history.py b/homeassistant_api/models/history.py index 5add5406..6baa3751 100644 --- a/homeassistant_api/models/history.py +++ b/homeassistant_api/models/history.py @@ -6,7 +6,7 @@ class History(BaseModel): - """Model representing past states of an entity.""" + """Model representing past :code:`State`'s of an entity.""" states: Tuple[State, ...] @@ -16,7 +16,7 @@ def __init__(self, *args, **kwargs): @property def entity_id(self) -> str: - """Returns the shared entity_id of states.""" + """Returns the shared :code:`entity_id` of states.""" entity_ids = [state.entity_id for state in self.states] result, *others = set(entity_ids) assert len(others) == 0 From b78bed1d246820dca8f4dfc4a37bc7505c4e4ef8 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Tue, 22 Feb 2022 00:56:37 +0000 Subject: [PATCH 09/11] Finished developer docs. --- docs/development.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/development.rst b/docs/development.rst index ea89ebaa..8020ff0c 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -10,6 +10,9 @@ See below. Contribution Ideas ********************* +If you don't know what you want to contribute yet you should take a look at the TODO.md in the :resource:`repository `. +We're always interested in integrating ways to make the library faster, extensible and easier to use. + Setting up your Development Environment ***************************************** @@ -59,10 +62,12 @@ Then you can test your changes by passing the API URL, and Long Lived Access Tok Code Styling Guidelines ************************** - -Catching Up With the Real World -********************************** - +In order to make sure that our code is easy to read, and navigate. +As well as to stop stupid mistakes like typos, undefined variables, etc. +We enforce code standards. +Using the tools, :code:`flake8`, :code:`pylint`, :code:`mypy`, :code:`black`, :code:`isort`, we make make sure that our code quality is top notch. +You can those tools manually your self, or you can run them all at once on your changes by running :code:`pre-commit run`. +Your code will also be checked again when you open a PR on the original repository. Merging Your Contributions ***************************** From 0bd710fccda517822f78e9868a869ade6635143d Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Tue, 22 Feb 2022 01:13:34 +0000 Subject: [PATCH 10/11] Fixed linting issues. --- .github/workflows/python-publish.yml | 5 ++++- docs/development.rst | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 62353925..cda181f8 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -24,4 +24,7 @@ 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 \ + --username $TWINE_USERNAME \ + --password TWINE_PASSWORD diff --git a/docs/development.rst b/docs/development.rst index 8020ff0c..1d9af6db 100644 --- a/docs/development.rst +++ b/docs/development.rst @@ -21,7 +21,7 @@ So now that you know what you want to contribute it is time to setup a developme Step One: Fork the Repository =============================== -Click `here `_ to fork the repostiory. The click your username. +Click `here `_ to fork the repository. Then click your username. Step Two: Clone the Repository Locally ======================================= @@ -72,7 +72,7 @@ Your code will also be checked again when you open a PR on the original reposito Merging Your Contributions ***************************** -Once you have tested your changes and commited them to your fork you can merge them back into the :resource:`original repository `. +Once you have tested your changes and committed them to your fork you can merge them back into the :resource:`original repository `. Head over to the :resource:`Pull Request Page ` and select your fork to merge into the `GrandMoff100/dev` branch. Then you can hit "Create Pull Request" and we'll review it as soon as possible. In order to be merged though, your code needs to follow our :ref:`Styling Guidelines `. From 3669582d8ac7c00325c5bfc71133f14df2596013 Mon Sep 17 00:00:00 2001 From: GrandMoff100 Date: Tue, 22 Feb 2022 01:16:09 +0000 Subject: [PATCH 11/11] [MegaLinter] Apply linters fixes --- .github/workflows/codeql-analysis.yml | 58 +++++++++++++-------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 9d4dc5a3..e45157da 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ dev ] + branches: [dev] pull_request: # The branches below must be a subset of the branches above - branches: [ dev ] + branches: [dev] schedule: - - cron: '17 7 * * 1' + - cron: "17 7 * * 1" jobs: analyze: @@ -32,39 +32,39 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'python' ] + language: ["python"] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - - name: Checkout repository - uses: actions/checkout@v2 + - name: Checkout repository + uses: actions/checkout@v2 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v1 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - # queries: ./path/to/local/query, your-org/your-repo/queries@main + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl + # â„šī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl - # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language + # âœī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language - #- run: | - # make bootstrap - # make release + #- run: | + # make bootstrap + # make release - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1