diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..9fda616 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,42 @@ +{ + "name": "ludeeus/integration_blueprint", + "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.10-bullseye", + "postCreateCommand": "scripts/setup", + "forwardPorts": [ + 8123 + ], + "portsAttributes": { + "8123": { + "label": "Home Assistant", + "onAutoForward": "notify" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "github.vscode-pull-request-github", + "ryanluker.vscode-coverage-gutters", + "ms-python.vscode-pylance" + ], + "settings": { + "files.eol": "\n", + "editor.tabSize": 4, + "python.pythonPath": "/usr/bin/python3", + "python.analysis.autoSearchPaths": false, + "python.linting.pylintEnabled": true, + "python.linting.enabled": true, + "python.formatting.provider": "black", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } + } + }, + "remoteUser": "vscode", + "features": { + "ghcr.io/devcontainers/features/rust:1": {} + } +} diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index 75de849..0000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,122 +0,0 @@ -There are two ways to use devcontainer: the original one with Visual Studio created by ludeeus, and the alternative one without any editor I created. - -## Developing with Visual Studio Code + devcontainer - -The easiest way to get started with custom integration development is to use Visual Studio Code with devcontainers. This approach will create a preconfigured development environment with all the tools you need. - -In the container you will have a dedicated Home Assistant core instance running with your custom component code. You can configure this instance by updating the `./devcontainer/configuration.yaml` file. - -**Prerequisites** - -- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- Docker - - For Linux, macOS, or Windows 10 Pro/Enterprise/Education use the [current release version of Docker](https://docs.docker.com/install/) - - Windows 10 Home requires [WSL 2](https://docs.microsoft.com/windows/wsl/wsl2-install) and the current Edge version of Docker Desktop (see instructions [here](https://docs.docker.com/docker-for-windows/wsl-tech-preview/)). This can also be used for Windows Pro/Enterprise/Education. -- [Visual Studio code](https://code.visualstudio.com/) -- [Remote - Containers (VSC Extension)][extension-link] - -[More info about requirements and devcontainer in general](https://code.visualstudio.com/docs/remote/containers#_getting-started) - -[extension-link]: https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers - -**Getting started:** - -1. Fork the repository. -2. Clone the repository to your computer. -3. Open the repository using Visual Studio code. - -When you open this repository with Visual Studio code you are asked to "Reopen in Container", this will start the build of the container. - -_If you don't see this notification, open the command palette and select `Remote-Containers: Reopen Folder in Container`._ - -### Tasks - -The devcontainer comes with some useful tasks to help you with development, you can start these tasks by opening the command palette and select `Tasks: Run Task` then select the task you want to run. - -When a task is currently running (like `Run Home Assistant on port 9123` for the docs), it can be restarted by opening the command palette and selecting `Tasks: Restart Running Task`, then select the task you want to restart. - -The available tasks are: - -Task | Description --- | -- -Run Home Assistant on port 9123 | Launch Home Assistant with your custom component code and the configuration defined in `.devcontainer/configuration.yaml`. -Run Home Assistant configuration against /config | Check the configuration. -Upgrade Home Assistant to latest dev | Upgrade the Home Assistant core version in the container to the latest version of the `dev` branch. -Install a specific version of Home Assistant | Install a specific version of Home Assistant core in the container. - -### Step by Step debugging - -With the development container, -you can test your custom component in Home Assistant with step by step debugging. - -You need to modify the `configuration.yaml` file in `.devcontainer` folder -by uncommenting the line: - -```yaml -# debugpy: -``` - -Then launch the task `Run Home Assistant on port 9123`, and launch the debugger -with the existing debugging configuration `Python: Attach Local`. - -For more information, look at [the Remote Python Debugger integration documentation](https://www.home-assistant.io/integrations/debugpy/). - -## Developing with any editors + devcontainer - -This repository inherits the integration_blueprint repository. Since I do not use Visual Studio, I left all its settings, but made my own mechanism for quick and convenient code testing, which works with absolutely any editor (I use PyCharm). And it can even be run on a remote machine. The only limitation of this method is that it needs Linux to work. Perhaps, it might work on other systems as well, but I haven't tested it, sorry. - -Similar first way in the container you will have a dedicated Home Assistant core instance running with your custom component code. You can configure this instance by updating the `./devcontainer/configuration.yaml` file. - -**Prerequisites** - -- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) -- Docker - - For Linux, macOS, or Windows 10 Pro/Enterprise/Education use the [current release version of Docker](https://docs.docker.com/install/) - - Windows 10 Home requires [WSL 2](https://docs.microsoft.com/windows/wsl/wsl2-install) and the current Edge version of Docker Desktop (see instructions [here](https://docs.docker.com/docker-for-windows/wsl-tech-preview/)). This can also be used for Windows Pro/Enterprise/Education. -- Bash environment - -**Getting started:** - -1. Fork the repository. -2. Clone the repository to your computer. -3. Run devcontainer from command line: \ - `./bin/devcontainer start` - -Note: By default, the container is created in local access only mode. -If you want to have access from the outside (for example, when starting on a remote machine), add parameter `--public` at the first start (after creating the container, this setting is remembered by the system): \ -`./bin/devcontainer --public start` - -While container is run Home Assistant will be available at http://localhost:9123/ - -Note: The container can be stopped by pressing the good old Ctrl-C keys. - -### Useful devcontainer commands - -- Start container: `./bin/devcontainer start` -- Destroy container: `./bin/devcontainer down` -- Upgrade Home Assistant to latest dev: `./bin/devcontainer upgrade` -- Install a specific version of Home Assistant: `./bin/devcontainer set-version` -- Run command line into container: `./bin/devcontainer bash` - -### Bootstrap script - -Sometimes you need to perform additional steps at the stage of container initialization. To do this, you can use script `.devcontainer/bootstrap.sh`. By default, it is configured to automatically install the libraries required for your component from private repositories. But you are free to change it however you like. -The main thing to remember is that the script will be executed INSIDE the container. - -The script is automatically executed immediately after initializing the container and immediately after upgrading the HA version. It can also be executed at any time with command `./bin/devcontainer bootstrap`. - -When the script is run, it receives only one parameter — the current container command: `install`, `upgrade` or `bootstrap`. -The current directory when running the script is the root directory of the repository inside the container. Home Assistant settings are located in directory `/config`. - -To automatically install libraries from private repositories, you need to register each such library in the `requirements.txt` file as a link of the form -```requirements.txt -git+https://{GITHUB_TOKEN}@github.com//.git@#egg= -``` -Note: `{GITHUB_TOKEN}` is text as is, NOT actual token! - -After that, in the root of the repository, create a file `secrets.yaml` and write the contents to it: -```yaml -github_token: -``` - -When you run the bootstrap script, it will automatically extract the required lines from requirements.txt, replace line `{GITHUB_TOKEN}` in them with the real token, and install the libraries. At the same time, file `secrets.yaml` is prohibited via `.gitignore` from being sent to the repository. Therefore, you can not be afraid that the token will be compromised. diff --git a/.devcontainer/configuration.yaml b/.devcontainer/configuration.yaml deleted file mode 100644 index 53b8c09..0000000 --- a/.devcontainer/configuration.yaml +++ /dev/null @@ -1,9 +0,0 @@ -default_config: - -logger: - default: info - logs: - custom_components.integration_blueprint: debug - -# If you need to debug uncomment the line below (doc: https://www.home-assistant.io/integrations/debugpy/) -# debugpy: diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 0761dae..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,30 +0,0 @@ -// See https://aka.ms/vscode-remote/devcontainer.json for format details. -{ - "image": "ghcr.io/ludeeus/devcontainer/integration:stable", - "name": "Blueprint integration development", - "context": "..", - "appPort": [ - "9123:8123" - ], - "postCreateCommand": "container install", - "extensions": [ - "ms-python.python", - "github.vscode-pull-request-github", - "ryanluker.vscode-coverage-gutters", - "ms-python.vscode-pylance" - ], - "settings": { - "files.eol": "\n", - "editor.tabSize": 4, - "terminal.integrated.shell.linux": "/bin/bash", - "python.pythonPath": "/usr/bin/python3", - "python.analysis.autoSearchPaths": false, - "python.linting.pylintEnabled": true, - "python.linting.enabled": true, - "python.formatting.provider": "black", - "editor.formatOnPaste": false, - "editor.formatOnSave": true, - "editor.formatOnType": true, - "files.trimTrailingWhitespace": true - } -} diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000..9c65fef --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,55 @@ +--- +name: "Bug report" +description: "Report a bug with the integration" +labels: "Bug" +body: +- type: markdown + attributes: + value: Before you open a new issue, search through the existing issues to see if others have had the same problem. +- type: textarea + attributes: + label: "System Health details" + description: "Paste the data from the System Health card in Home Assistant (https://www.home-assistant.io//more-info/system-health#github-issues)" + validations: + required: true +- type: checkboxes + attributes: + label: Checklist + options: + - label: I have enabled debug logging for my installation. + required: true + - label: I have filled out the issue template to the best of my ability. + required: true + - label: This issue only contains 1 issue (if you have multiple issues, open one issue for each issue). + required: true + - label: This issue is not a duplicate issue of currently [previous issues](https://github.com/ludeeus/integration_blueprint/issues?q=is%3Aissue+label%3A%22Bug%22+).. + required: true +- type: textarea + attributes: + label: "Describe the issue" + description: "A clear and concise description of what the issue is." + validations: + required: true +- type: textarea + attributes: + label: Reproduction steps + description: "Without steps to reproduce, it will be hard to fix, it is very important that you fill out this part, issues without it will be closed" + value: | + 1. + 2. + 3. + ... + validations: + required: true +- type: textarea + attributes: + label: "Debug logs" + description: "To enable debug logs check this https://www.home-assistant.io/integrations/logger/, this **needs** to include _everything_ from startup of Home Assistant to the point where you encounter the issue." + render: text + validations: + required: true + +- type: textarea + attributes: + label: "Diagnostics dump" + description: "Drag the diagnostics dump file here. (see https://www.home-assistant.io/integrations/diagnostics/ for info)" diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 41a2793..3ba13e0 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1 @@ blank_issues_enabled: false -#contact_links: -# - name: GitHub Community Forum -# url: https://github.community/ -# about: Please ask and answer questions here. -# - name: GitHub Security Bug Bounty -# url: https://bounty.github.com/ -# about: Please report security vulnerabilities here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..433467b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,47 @@ +--- +name: "Feature request" +description: "Suggest an idea for this project" +labels: "Feature+Request" +body: +- type: markdown + attributes: + value: Before you open a new feature request, search through the existing feature requests to see if others have had the same idea. +- type: checkboxes + attributes: + label: Checklist + options: + - label: I have filled out the template to the best of my ability. + required: true + - label: This only contains 1 feature request (if you have multiple feature requests, open one feature request for each feature request). + required: true + - label: This issue is not a duplicate feature request of [previous feature requests](https://github.com/ludeeus/integration_blueprint/issues?q=is%3Aissue+label%3A%22Feature+Request%22+). + required: true + +- type: textarea + attributes: + label: "Is your feature request related to a problem? Please describe." + description: "A clear and concise description of what the problem is." + placeholder: "I'm always frustrated when [...]" + validations: + required: true + +- type: textarea + attributes: + label: "Describe the solution you'd like" + description: "A clear and concise description of what you want to happen." + validations: + required: true + +- type: textarea + attributes: + label: "Describe alternatives you've considered" + description: "A clear and concise description of any alternative solutions or features you've considered." + validations: + required: true + +- type: textarea + attributes: + label: "Additional context" + description: "Add any other context or screenshots about the feature request here." + validations: + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cf30ba9..eee9634 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,7 @@ -# Basic set up for three package managers - +# https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file version: 2 updates: - package-ecosystem: "github-actions" - # Workflow files stored in the - # default location of `.github/workflows` directory: "/" schedule: interval: "weekly" @@ -12,4 +9,7 @@ updates: - package-ecosystem: "pip" directory: "/" schedule: - interval: "daily" + interval: "weekly" + ignore: + # Dependabot should not update Home Assistant as that should match the homeassistant key in hacs.json + - dependency-name: "homeassistant" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..28f9fce --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,29 @@ +name: "Lint" + +on: + push: + branches: + - "main" + pull_request: + branches: + - "main" + +jobs: + ruff: + name: "Ruff" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout the repository" + uses: "actions/checkout@v3.5.2" + + - name: "Set up Python" + uses: actions/setup-python@v4.6.1 + with: + python-version: "3.10" + cache: "pip" + + - name: "Install requirements" + run: python3 -m pip install -r requirements.txt + + - name: "Run" + run: python3 -m ruff check . diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..6d257aa --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,37 @@ +name: "Validate" + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + push: + branches: + - "main" + pull_request: + branches: + - "main" + +jobs: + hassfest: # https://developers.home-assistant.io/blog/2020/04/16/hassfest + name: "Hassfest Validation" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout the repository" + uses: "actions/checkout@v3.5.2" + + - name: "Run hassfest validation" + uses: "home-assistant/actions/hassfest@master" + + hacs: # https://github.com/hacs/action + name: "HACS Validation" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout the repository" + uses: "actions/checkout@v3.5.2" + + - name: "Run HACS validation" + uses: "hacs/action@main" + with: + category: "integration" + # Remove this 'ignore' key when you have added brand images for your integration to https://github.com/home-assistant/brands + ignore: "brands" diff --git a/.gitignore b/.gitignore index d2a81ce..b1d2583 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,9 @@ __pycache__/ # Distribution / packaging .Python -build/ +*/build/* develop-eggs/ -dist/ +*/dist/* downloads/ eggs/ .eggs/ @@ -36,7 +36,7 @@ nosetests.xml coverage.xml *.cover .hypothesis/ -.pytest_cache/ +.pytest* # Sphinx documentation docs/_build/ @@ -53,11 +53,13 @@ pythonenv* .venv .coverage .idea +.vscode env/ venv/ ENV/ env.bak/ venv.bak/ +coverage.xml # mkdocs documentation /site @@ -65,6 +67,10 @@ venv.bak/ # mypy .mypy_cache/ +# Home Assistant configuration +config/* +!config/configuration.yaml + /custom_components/__init__.py diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b9db6ed..ea74596 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ repos: hooks: - id: manifest name: Update manifest.json - entry: bin/update_manifest + entry: scripts/update_manifest language: script files: ^(custom_components/.+/const\.py|requirements\.txt)$ - repo: https://github.com/charliermarsh/ruff-pre-commit @@ -69,20 +69,20 @@ repos: # shell. - id: mypy name: mypy - entry: bin/run-in-env mypy + entry: scripts/run-in-env mypy language: script types: [python] require_serial: true files: ^custom_components/.+\.py$ - id: pylint name: pylint - entry: bin/run-in-env python3 -m pylint + entry: scripts/run-in-env python3 -m pylint language: system types: [python] # Uncomment lines below to test code on every commit # - id: pytest # name: pytest -# entry: bin/run-in-env pytest +# entry: scripts/run-in-env pytest # language: system # pass_filenames: false # always_run: true diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..7a8331a --- /dev/null +++ b/.ruff.toml @@ -0,0 +1,48 @@ +# The contents of this file is based on https://github.com/home-assistant/core/blob/dev/pyproject.toml + +target-version = "py310" + +select = [ + "B007", # Loop control variable {name} not used within loop body + "B014", # Exception handler with duplicate exception + "C", # complexity + "D", # docstrings + "E", # pycodestyle + "F", # pyflakes/autoflake + "ICN001", # import concentions; {name} should be imported as {asname} + "PGH004", # Use specific rule codes when using noqa + "PLC0414", # Useless import alias. Import alias does not rename original package. + "SIM105", # Use contextlib.suppress({exception}) instead of try-except-pass + "SIM117", # Merge with-statements that use the same scope + "SIM118", # Use {key} in {dict} instead of {key} in {dict}.keys() + "SIM201", # Use {left} != {right} instead of not {left} == {right} + "SIM212", # Use {a} if {a} else {b} instead of {b} if not {a} else {a} + "SIM300", # Yoda conditions. Use 'age == 42' instead of '42 == age'. + "SIM401", # Use get from dict with default instead of an if block + "T20", # flake8-print + "TRY004", # Prefer TypeError exception for invalid type + "RUF006", # Store a reference to the return value of asyncio.create_task + "UP", # pyupgrade + "W", # pycodestyle +] + +ignore = [ + "D202", # No blank lines allowed after function docstring + "D203", # 1 blank line required before class docstring + "D213", # Multi-line docstring summary should start at the second line + "D404", # First word of the docstring should not be This + "D406", # Section name should end with a newline + "D407", # Section name underlining + "D411", # Missing blank line before section + "E501", # line too long + "E731", # do not assign a lambda expression, use a def +] + +[flake8-pytest-style] +fixture-parentheses = false + +[pyupgrade] +keep-runtime-typing = true + +[mccabe] +max-complexity = 25 \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index df772bc..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - // Example of attaching to local debug server - "name": "Python: Attach Local", - "type": "python", - "request": "attach", - "port": 5678, - "host": "localhost", - "pathMappings": [ - { - "localRoot": "${workspaceFolder}", - "remoteRoot": "." - } - ] - }, - { - // Example of attaching to my production server - "name": "Python: Attach Remote", - "type": "python", - "request": "attach", - "port": 5678, - "host": "homeassistant.local", - "pathMappings": [ - { - "localRoot": "${workspaceFolder}", - "remoteRoot": "/usr/src/homeassistant" - } - ] - } - ] - } diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index a3d535d..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "python.linting.pylintEnabled": true, - "python.linting.enabled": true, - "python.pythonPath": "/usr/local/bin/python", - "files.associations": { - "*.yaml": "home-assistant" - } -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 7ab4ba8..3aa1c50 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,27 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "Run Home Assistant on port 9123", + "label": "Run Home Assistant on port 8123", "type": "shell", - "command": "container start", - "problemMatcher": [] - }, - { - "label": "Run Home Assistant configuration against /config", - "type": "shell", - "command": "container check", - "problemMatcher": [] - }, - { - "label": "Upgrade Home Assistant to latest dev", - "type": "shell", - "command": "container install", - "problemMatcher": [] - }, - { - "label": "Install a specific version of Home Assistant", - "type": "shell", - "command": "container set-version", + "command": "scripts/develop", "problemMatcher": [] } ] diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a1542b6..2fd9ce9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,10 +17,11 @@ Github is used to host code, to track issues and feature requests, as well as ac Pull requests are the best way to propose changes to the codebase. -1. Fork the repo and create your branch from `master`. +1. Fork the repo and create your branch from `main`. 2. If you've changed something, update the documentation. 3. Make sure your code lints (using black). -4. Issue that pull request! +4. Test you contribution. +5. Issue that pull request! ## Any contributions you make will be under the MIT Software License @@ -56,7 +57,7 @@ This custom component is based on [integration blueprint template](https://githu It comes with development environment in a container, easy to launch if you use Visual Studio Code. With this container you will have a stand alone Home Assistant instance running and already configured with the included -[`.devcontainer/configuration.yaml`](./.devcontainer/configuration.yaml) +[`configuration.yaml`](./config/configuration.yaml) file. ## License diff --git a/README.md b/README.md index 28b8bbd..122c4c7 100644 --- a/README.md +++ b/README.md @@ -22,35 +22,17 @@ This repository is an extension and addition to the [integration_blueprint](http This repository contains multiple files, here is a overview: -File | Purpose --- | -- -`.devcontainer/*` | Used for development/testing with VSCODE, more info in the readme file in that dir. -`.github/ISSUE_TEMPLATE/feature_request.md` | Template for Feature Requests -`.github/ISSUE_TEMPLATE/issue.md` | Template for issues -`.vscode/tasks.json` | Tasks for the devcontainer. -`custom_components/integration_blueprint/translations/*` | [Translation files.](https://developers.home-assistant.io/docs/internationalization/custom_integration) -`custom_components/integration_blueprint/__init__.py` | The component file for the integration. -`custom_components/integration_blueprint/api.py` | This is a sample API client. -`custom_components/integration_blueprint/binary_sensor.py` | Binary sensor platform for the integration. -`custom_components/integration_blueprint/config_flow.py` | Config flow file, this adds the UI configuration possibilities. -`custom_components/integration_blueprint/const.py` | A file to hold shared variables/constants for the entire integration. -`custom_components/integration_blueprint/manifest.json` | A [manifest file](https://developers.home-assistant.io/docs/en/creating_integration_manifest.html) for Home Assistant. -`custom_components/integration_blueprint/sensor.py` | Sensor platform for the integration. -`custom_components/integration_blueprint/switch.py` | Switch sensor platform for the integration. -`tests/__init__.py` | Makes the `tests` folder a module. -`tests/conftest.py` | Global [fixtures](https://docs.pytest.org/en/stable/fixture.html) used in tests to [patch](https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch) functions. -`tests/test_api.py` | Tests for `custom_components/integration_blueprint/api.py`. -`tests/test_config_flow.py` | Tests for `custom_components/integration_blueprint/config_flow.py`. -`tests/test_init.py` | Tests for `custom_components/integration_blueprint/__init__.py`. -`tests/test_switch.py` | Tests for `custom_components/integration_blueprint/switch.py`. -`CONTRIBUTING.md` | Guidelines on how to contribute. -`example.png` | Screenshot that demonstrate how it might look in the UI. -`info.md` | An example on a info file (used by [hacs][hacs]). -`LICENSE.md` | The license file for the project. -`README.md` | The file you are reading now, should contain info about the integration, installation and configuration instructions. -`requirements.txt` | Python packages used by this integration. -`requirements-dev.txt` | Python packages used to provide [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense)/code hints during development of this integration, typically includes packages in `requirements.txt` but may include additional packages -`requirements-text.txt` | Python packages required to run the tests for this integration, typically includes packages in `requirements-dev.txt` but may include additional packages +File | Purpose | Documentation +-- | -- | -- +`.devcontainer.json` | Used for development/testing with Visual Studio Code. | [Documentation](https://code.visualstudio.com/docs/remote/containers) +`.github/ISSUE_TEMPLATE/*.yml` | Templates for the issue tracker | [Documentation](https://help.github.com/en/github/building-a-strong-community/configuring-issue-templates-for-your-repository) +`.vscode/tasks.json` | Tasks for the devcontainer. | [Documentation](https://code.visualstudio.com/docs/editor/tasks) +`custom_components/integration_blueprint/*` | Integration files, this is where everything happens. | [Documentation](https://developers.home-assistant.io/docs/creating_component_index) +`tests/*` | Integration unit tests. | +`CONTRIBUTING.md` | Guidelines on how to contribute. | [Documentation](https://help.github.com/en/github/building-a-strong-community/setting-guidelines-for-repository-contributors) +`LICENSE` | The license file for the project. | [Documentation](https://help.github.com/en/github/creating-cloning-and-archiving-repositories/licensing-a-repository) +`README.md` | The file you are reading now, should contain info about the integration, installation and configuration instructions. | [Documentation](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax) +`requirements.txt` | Python packages used for development/lint/testing this integration. | [Documentation](https://pip.pypa.io/en/stable/user_guide/#requirements-files) ## How? @@ -108,8 +90,9 @@ After these steps, your repository will developing on a own branch. But in paral git merge blueprint/dev ``` -If you want to use all the potential and features of this blueprint template you -should use devcontainer. See [.devcontainer/README.md](./.devcontainer/README.md) for more information. +Then: +1. Rename all instances of the `integration_blueprint` to `custom_components/` (e.g. `custom_components/awesome_integration`). +1. Rename all instances of the `Integration Blueprint` to `` (e.g. `Awesome Integration`). If you need to work on the python library in parallel of this integration (`sampleclient` in this example) there are different options. The following one seems @@ -123,139 +106,11 @@ easy to implement: development branch and increase the number of the python library version in `manifest.json` file to ensure Home Assistant update the code of the python library. (example `"requirements": ["git+https://...==0.0.1beta2"]`). +## Next steps -*** -README content if this was a published component: -*** - -*Please :star: this repo if you find it useful* - -# integration_blueprint - -[![GitHub Release][releases-shield]][releases] -[![GitHub Activity][commits-shield]][commits] -[![License][license-shield]][license] - -[![hacs][hacs-shield]][hacs] -[![Project Maintenance][maintenance-shield]][user_profile] -[![Support me on Patreon][patreon-shield]][patreon] - -[![Community Forum][forum-shield]][forum] - -_Component to integrate with [integration_blueprint][component]._ - -**This component will set up the following platforms.** - -Platform | Description --- | -- -`binary_sensor` | Show something `True` or `False`. -`sensor` | Show info from blueprint API. -`switch` | Switch something `True` or `False`. - -![example][exampleimg] - -## Known Limitations and Issues - -- Some example limitation. - -## Installation - -### Install from HACS (recommended) - -1. Have [HACS][hacs] installed, this will allow you to easily manage and track updates. -1. Search for "Blueprint". -1. Click Install below the found integration. -1. _If you want to configure component via Home Assistant UI..._\ - in the HA UI go to "Configuration" > "Integrations" click "+" and search for "Integration blueprint". -1. _If you want to configure component via `configuration.yaml`..._\ - follow instructions below, then restart Home Assistant. - -### Manual installation - -1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). -1. If you do not have a `custom_components` directory (folder) there, you need to create it. -1. In the `custom_components` directory (folder) create a new folder called `integration_blueprint`. -1. Download file `integration_blueprint.zip` from the [latest release section][releases-latest] in this repository. -1. Extract _all_ files from this archive you downloaded in the directory (folder) you created. -1. Restart Home Assistant -1. _If you want to configure component via Home Assistant UI..._\ - in the HA UI go to "Configuration" > "Integrations" click "+" and search for "Blueprint". -1. _If you want to configure component via `configuration.yaml`..._\ - follow instructions below, then restart Home Assistant. - -

* * *

-I put a lot of work into making this repo and component available and updated to inspire and help others! I will be glad to receive thanks from you — it will give me new strength and add enthusiasm: -


-Patreon -
or support via Bitcoin or Etherium:
-Bitcoin
-16yfCfz9dZ8y8yuSwBFVfiAa3CNYdMh7Ts
-

- -## Configuration is done in the UI - - - -## Track updates - -You can automatically track new versions of this component and update it by [HACS][hacs]. - -## Troubleshooting - -To enable debug logs use this configuration: -```yaml -# Example configuration.yaml entry -logger: - default: info - logs: - custom_components.integration_blueprint: debug -``` -... then restart HA. - -## Contributions are welcome! - -This is an active open-source project. We are always open to people who want to -use the code or contribute to it. - -We have set up a separate document containing our -[contribution guidelines](CONTRIBUTING.md). - -Thank you for being involved! :heart_eyes: - -## Authors & contributors - -The original setup of this component is by [Andrey "Limych" Khrolenok](https://github.com/Limych). - -For a full list of all authors and contributors, -check [the contributor's page][contributors]. - -This Home Assistant custom component was created and is updated using the [HA-Blueprint template](https://github.com/Limych/ha-blueprint). You can use this template to maintain your own Home Assistant custom components. - -## License - -creative commons Attribution-NonCommercial-ShareAlike 4.0 International License - -See separate [license file](LICENSE.md) for full text. - -*** - -[component]: https://github.com/Limych/ha-blueprint -[commits-shield]: https://img.shields.io/github/commit-activity/y/Limych/ha-blueprint.svg?style=popout -[commits]: https://github.com/Limych/ha-blueprint/commits/master -[hacs-shield]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=popout -[hacs]: https://hacs.xyz -[exampleimg]: example.png -[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=popout -[forum]: https://community.home-assistant.io/ -[license]: https://github.com/Limych/ha-blueprint/blob/main/LICENSE.md -[license-shield]: https://img.shields.io/badge/license-Creative_Commons_BY--NC--SA_License-lightgray.svg?style=popout -[maintenance-shield]: https://img.shields.io/badge/maintainer-Andrey%20Khrolenok%20%40Limych-blue.svg?style=popout -[releases-shield]: https://img.shields.io/github/release/Limych/ha-blueprint.svg?style=popout -[releases]: https://github.com/Limych/ha-blueprint/releases -[releases-latest]: https://github.com/Limych/ha-blueprint/releases/latest -[user_profile]: https://github.com/Limych -[report_bug]: https://github.com/Limych/ha-blueprint/issues/new?template=bug_report.md -[suggest_idea]: https://github.com/Limych/ha-blueprint/issues/new?template=feature_request.md -[contributors]: https://github.com/Limych/ha-blueprint/graphs/contributors -[patreon-shield]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3DLimych%26type%3Dpatrons&style=popout -[patreon]: https://www.patreon.com/join/limych +These are some next steps you may want to look into: +- Add tests to your integration, [`pytest-homeassistant-custom-component`](https://github.com/MatthewFlamm/pytest-homeassistant-custom-component) can help you get started. +- Add brand images (logo/icon) to https://github.com/home-assistant/brands. +- Create your first release. +- Share your integration on the [Home Assistant Forum](https://community.home-assistant.io/). +- Submit your integration to the [HACS](https://hacs.xyz/docs/publish/start). diff --git a/README_EXAMPLE.md b/README_EXAMPLE.md new file mode 100644 index 0000000..5e89a03 --- /dev/null +++ b/README_EXAMPLE.md @@ -0,0 +1,129 @@ +*Please :star: this repo if you find it useful* + +# Integration Blueprint + +[![GitHub Release][releases-shield]][releases] +[![GitHub Activity][commits-shield]][commits] +[![License][license-shield]](LICENSE) + +[![hacs][hacs-shield]][hacs] +[![Project Maintenance][maintenance-shield]][user_profile] +[![Support me on Patreon][patreon-shield]][patreon] + +[![Community Forum][forum-shield]][forum] + +_Integration to integrate with [integration_blueprint][integration_blueprint]._ + +**This integration will set up the following platforms.** + +Platform | Description +-- | -- +`binary_sensor` | Show something `True` or `False`. +`sensor` | Show info from blueprint API. +`switch` | Switch something `True` or `False`. + +## Known Limitations and Issues + +- Some example limitation. + +## Installation + +### Install from HACS (recommended) + +1. Have [HACS][hacs] installed, this will allow you to easily manage and track updates. +1. Search for "Blueprint". +1. Click Install below the found integration. +1. _If you want to configure component via Home Assistant UI..._\ + in the HA UI go to "Configuration" > "Integrations" click "+" and search for "Integration blueprint". +1. _If you want to configure component via `configuration.yaml`..._\ + follow instructions below, then restart Home Assistant. + +### Manual installation + +1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`). +1. If you do not have a `custom_components` directory (folder) there, you need to create it. +1. In the `custom_components` directory (folder) create a new folder called `integration_blueprint`. +1. Download file `integration_blueprint.zip` from the [latest release section][releases-latest] in this repository. +1. Extract _all_ files from this archive you downloaded in the directory (folder) you created. +1. Restart Home Assistant +1. _If you want to configure component via Home Assistant UI..._\ + in the HA UI go to "Configuration" > "Integrations" click "+" and search for "Blueprint". +1. _If you want to configure component via `configuration.yaml`..._\ + follow instructions below, then restart Home Assistant. + +

* * *

+I put a lot of work into making this repo and component available and updated to inspire and help others! I will be glad to receive thanks from you — it will give me new strength and add enthusiasm: +


+Patreon +
or support via Bitcoin or Etherium:
+Bitcoin
+16yfCfz9dZ8y8yuSwBFVfiAa3CNYdMh7Ts
+

+ +## Configuration is done in the UI + + + +## Track updates + +You can automatically track new versions of this component and update it by [HACS][hacs]. + +## Troubleshooting + +To enable debug logs use this configuration: +```yaml +# Example configuration.yaml entry +logger: + default: info + logs: + custom_components.integration_blueprint: debug +``` +... then restart HA. + +## Contributions are welcome! + +This is an active open-source project. We are always open to people who want to +use the code or contribute to it. + +We have set up a separate document containing our +[contribution guidelines](CONTRIBUTING.md). + +Thank you for being involved! :heart_eyes: + +## Authors & contributors + +The original setup of this component is by [Andrey "Limych" Khrolenok](https://github.com/Limych). + +For a full list of all authors and contributors, +check [the contributor's page][contributors]. + +This Home Assistant custom component was created and is updated using the [HA-Blueprint template](https://github.com/Limych/ha-blueprint). You can use this template to maintain your own Home Assistant custom components. + +## License + +creative commons Attribution-NonCommercial-ShareAlike 4.0 International License + +See separate [license file](LICENSE.md) for full text. + +*** + +[component]: https://github.com/Limych/ha-blueprint +[commits-shield]: https://img.shields.io/github/commit-activity/y/Limych/ha-blueprint.svg?style=popout +[commits]: https://github.com/Limych/ha-blueprint/commits/master +[hacs-shield]: https://img.shields.io/badge/HACS-Custom-orange.svg?style=popout +[hacs]: https://hacs.xyz +[exampleimg]: example.png +[forum-shield]: https://img.shields.io/badge/community-forum-brightgreen.svg?style=popout +[forum]: https://community.home-assistant.io/ +[license]: https://github.com/Limych/ha-blueprint/blob/main/LICENSE.md +[license-shield]: https://img.shields.io/badge/license-Creative_Commons_BY--NC--SA_License-lightgray.svg?style=popout +[maintenance-shield]: https://img.shields.io/badge/maintainer-Andrey%20Khrolenok%20%40Limych-blue.svg?style=popout +[releases-shield]: https://img.shields.io/github/release/Limych/ha-blueprint.svg?style=popout +[releases]: https://github.com/Limych/ha-blueprint/releases +[releases-latest]: https://github.com/Limych/ha-blueprint/releases/latest +[user_profile]: https://github.com/Limych +[report_bug]: https://github.com/Limych/ha-blueprint/issues/new?template=bug_report.md +[suggest_idea]: https://github.com/Limych/ha-blueprint/issues/new?template=feature_request.md +[contributors]: https://github.com/Limych/ha-blueprint/graphs/contributors +[patreon-shield]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Fshieldsio-patreon.vercel.app%2Fapi%3Fusername%3DLimych%26type%3Dpatrons&style=popout +[patreon]: https://www.patreon.com/join/limych diff --git a/config/configuration.yaml b/config/configuration.yaml new file mode 100644 index 0000000..8c0d4e4 --- /dev/null +++ b/config/configuration.yaml @@ -0,0 +1,8 @@ +# https://www.home-assistant.io/integrations/default_config/ +default_config: + +# https://www.home-assistant.io/integrations/logger/ +logger: + default: info + logs: + custom_components.integration_blueprint: debug diff --git a/custom_components/integration_blueprint/__init__.py b/custom_components/integration_blueprint/__init__.py index aa5929c..a9adfdc 100644 --- a/custom_components/integration_blueprint/__init__.py +++ b/custom_components/integration_blueprint/__init__.py @@ -1,99 +1,51 @@ """Custom integration to integrate integration_blueprint with Home Assistant. For more details about this integration, please refer to -https://github.com/Limych/ha-blueprint +https://github.com/ludeeus/integration_blueprint """ -import asyncio -from datetime import timedelta -import logging +from __future__ import annotations from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_PASSWORD, CONF_USERNAME +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.typing import ConfigType -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .api import IntegrationBlueprintApiClient -from .const import DOMAIN, PLATFORMS, STARTUP_MESSAGE +from .const import DOMAIN +from .coordinator import BlueprintDataUpdateCoordinator -SCAN_INTERVAL = timedelta(seconds=30) - -_LOGGER: logging.Logger = logging.getLogger(__package__) - - -async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: - """Set up this integration using YAML.""" - return True +PLATFORMS: list[Platform] = [ + Platform.SENSOR, + Platform.BINARY_SENSOR, + Platform.SWITCH, +] +# https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up this integration using UI.""" - if hass.data.get(DOMAIN) is None: - hass.data.setdefault(DOMAIN, {}) - _LOGGER.info(STARTUP_MESSAGE) - - username = entry.data.get(CONF_USERNAME) - password = entry.data.get(CONF_PASSWORD) - - session = async_get_clientsession(hass) - client = IntegrationBlueprintApiClient(username, password, session) - - coordinator = BlueprintDataUpdateCoordinator(hass, client=client) - await coordinator.async_refresh() - - if not coordinator.last_update_success: - raise ConfigEntryNotReady - - hass.data[DOMAIN][entry.entry_id] = coordinator - - for platform in PLATFORMS: - if entry.options.get(platform, True): - coordinator.platforms.append(platform) - hass.async_add_job( - hass.config_entries.async_forward_entry_setup(entry, platform) - ) + hass.data.setdefault(DOMAIN, {}) + hass.data[DOMAIN][entry.entry_id] = coordinator = BlueprintDataUpdateCoordinator( + hass=hass, + client=IntegrationBlueprintApiClient( + username=entry.data[CONF_USERNAME], + password=entry.data[CONF_PASSWORD], + session=async_get_clientsession(hass), + ), + ) + # https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities + await coordinator.async_config_entry_first_refresh() + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) entry.async_on_unload(entry.add_update_listener(async_reload_entry)) - return True - - -class BlueprintDataUpdateCoordinator(DataUpdateCoordinator): - """Class to manage fetching data from the API.""" - - def __init__( - self, hass: HomeAssistant, client: IntegrationBlueprintApiClient - ) -> None: - """Initialize.""" - self.api = client - self.platforms = [] - super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL) - - async def _async_update_data(self): - """Update data via library.""" - try: - return await self.api.async_get_data() - except Exception as exception: - raise UpdateFailed() from exception + return True async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Handle removal of an entry.""" - coordinator = hass.data[DOMAIN][entry.entry_id] - unloaded = all( - await asyncio.gather( - *[ - hass.config_entries.async_forward_entry_unload(entry, platform) - for platform in PLATFORMS - if platform in coordinator.platforms - ] - ) - ) - if unloaded: + if unloaded := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): hass.data[DOMAIN].pop(entry.entry_id) - return unloaded diff --git a/custom_components/integration_blueprint/api.py b/custom_components/integration_blueprint/api.py index 5b153ef..a738040 100644 --- a/custom_components/integration_blueprint/api.py +++ b/custom_components/integration_blueprint/api.py @@ -1,85 +1,90 @@ """Sample API Client.""" +from __future__ import annotations + import asyncio -import logging import socket import aiohttp import async_timeout -TIMEOUT = 10 +class IntegrationBlueprintApiClientError(Exception): + """Exception to indicate a general API error.""" + + +class IntegrationBlueprintApiClientCommunicationError( + IntegrationBlueprintApiClientError +): + """Exception to indicate a communication error.""" -_LOGGER: logging.Logger = logging.getLogger(__package__) -HEADERS = {"Content-type": "application/json; charset=UTF-8"} +class IntegrationBlueprintApiClientAuthenticationError( + IntegrationBlueprintApiClientError +): + """Exception to indicate an authentication error.""" class IntegrationBlueprintApiClient: - """Blueprint API client class.""" + """Sample API Client.""" def __init__( - self, username: str, password: str, session: aiohttp.ClientSession + self, + username: str, + password: str, + session: aiohttp.ClientSession, ) -> None: """Sample API Client.""" self._username = username self._password = password self._session = session - async def async_get_data(self) -> dict: + async def async_get_data(self) -> any: """Get data from the API.""" - url = "https://jsonplaceholder.typicode.com/posts/1" - return await self.api_wrapper("get", url) + return await self._api_wrapper( + method="get", url="https://jsonplaceholder.typicode.com/posts/1" + ) - async def async_set_title(self, value: str) -> None: + async def async_set_title(self, value: str) -> any: """Get data from the API.""" - url = "https://jsonplaceholder.typicode.com/posts/1" - await self.api_wrapper("patch", url, data={"title": value}, headers=HEADERS) - - async def api_wrapper(self, method: str, url: str, data=None, headers=None) -> dict: + return await self._api_wrapper( + method="patch", + url="https://jsonplaceholder.typicode.com/posts/1", + data={"title": value}, + headers={"Content-type": "application/json; charset=UTF-8"}, + ) + + async def _api_wrapper( + self, + method: str, + url: str, + data: dict | None = None, + headers: dict | None = None, + ) -> any: """Get information from the API.""" - if data is None: - data = {} - if headers is None: - headers = {} try: - async with async_timeout.timeout(TIMEOUT): - if method == "get": # pylint: disable=no-else-return - response = await self._session.get(url, headers=headers) - return await response.json() - - elif method == "put": - await self._session.put(url, headers=headers, json=data) - - elif method == "patch": - await self._session.patch(url, headers=headers, json=data) - - elif method == "post": - await self._session.post(url, headers=headers, json=data) + async with async_timeout.timeout(10): + response = await self._session.request( + method=method, + url=url, + headers=headers, + json=data, + ) + if response.status in (401, 403): + raise IntegrationBlueprintApiClientAuthenticationError( + "Invalid credentials", + ) + response.raise_for_status() + return await response.json() except asyncio.TimeoutError as exception: - _LOGGER.error( - "Timeout error fetching information from %s - %s", - url, - exception, - ) - raise exception - - except (KeyError, TypeError) as exception: - _LOGGER.error( - "Error parsing information from %s - %s", - url, - exception, - ) - raise exception - + raise IntegrationBlueprintApiClientCommunicationError( + "Timeout error fetching information", + ) from exception except (aiohttp.ClientError, socket.gaierror) as exception: - _LOGGER.error( - "Error fetching information from %s - %s", - url, - exception, - ) - raise exception - + raise IntegrationBlueprintApiClientCommunicationError( + "Error fetching information", + ) from exception except Exception as exception: # pylint: disable=broad-except - _LOGGER.error("Something really wrong happened! - %s", exception) - raise exception + raise IntegrationBlueprintApiClientError( + "Something really wrong happened!" + ) from exception diff --git a/custom_components/integration_blueprint/binary_sensor.py b/custom_components/integration_blueprint/binary_sensor.py index 4684cd3..fff5b21 100644 --- a/custom_components/integration_blueprint/binary_sensor.py +++ b/custom_components/integration_blueprint/binary_sensor.py @@ -1,32 +1,50 @@ """Binary sensor platform for integration_blueprint.""" -from homeassistant.components.binary_sensor import BinarySensorEntity -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from __future__ import annotations -from .const import BINARY_SENSOR_DEVICE_CLASS, DEFAULT_NAME, DOMAIN +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, + BinarySensorEntityDescription, +) + +from .const import DOMAIN +from .coordinator import BlueprintDataUpdateCoordinator from .entity import IntegrationBlueprintEntity +ENTITY_DESCRIPTIONS = ( + BinarySensorEntityDescription( + key="integration_blueprint", + name="Integration Blueprint Binary Sensor", + device_class=BinarySensorDeviceClass.CONNECTIVITY, + ), +) + -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): - """Set up binary_sensor platform.""" +async def async_setup_entry(hass, entry, async_add_devices): + """Set up the binary_sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([IntegrationBlueprintBinarySensor(coordinator, entry)]) + async_add_devices( + IntegrationBlueprintBinarySensor( + coordinator=coordinator, + entity_description=entity_description, + ) + for entity_description in ENTITY_DESCRIPTIONS + ) class IntegrationBlueprintBinarySensor(IntegrationBlueprintEntity, BinarySensorEntity): """integration_blueprint binary_sensor class.""" - @property - def name(self): - """Return the name of the binary_sensor.""" - return f"{DEFAULT_NAME}_{Platform.BINARY_SENSOR}" - - @property - def device_class(self): - """Return the class of this binary_sensor.""" - return BINARY_SENSOR_DEVICE_CLASS + def __init__( + self, + coordinator: BlueprintDataUpdateCoordinator, + entity_description: BinarySensorEntityDescription, + ) -> None: + """Initialize the binary_sensor class.""" + super().__init__(coordinator) + self.entity_description = entity_description @property - def is_on(self): + def is_on(self) -> bool: """Return true if the binary_sensor is on.""" return self.coordinator.data.get("title", "") == "foo" diff --git a/custom_components/integration_blueprint/config_flow.py b/custom_components/integration_blueprint/config_flow.py index b422fce..c5a574f 100644 --- a/custom_components/integration_blueprint/config_flow.py +++ b/custom_components/integration_blueprint/config_flow.py @@ -1,118 +1,81 @@ """Adds config flow for Blueprint.""" -from typing import Optional +from __future__ import annotations import voluptuous as vol from homeassistant import config_entries -from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_USERNAME -from homeassistant.core import callback +from homeassistant.helpers import selector from homeassistant.helpers.aiohttp_client import async_create_clientsession -from homeassistant.helpers.typing import ConfigType -from .api import IntegrationBlueprintApiClient -from .const import DOMAIN, PLATFORMS # pylint: disable=unused-import +from .api import ( + IntegrationBlueprintApiClient, + IntegrationBlueprintApiClientAuthenticationError, + IntegrationBlueprintApiClientCommunicationError, + IntegrationBlueprintApiClientError, +) +from .const import DOMAIN, LOGGER class BlueprintFlowHandler(config_entries.ConfigFlow, domain=DOMAIN): """Config flow for Blueprint.""" VERSION = 1 - CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL - def __init__(self): - """Initialize.""" - self._errors = {} - - async def async_step_user(self, user_input: Optional[ConfigType] = None): + async def async_step_user( + self, + user_input: dict | None = None, + ) -> config_entries.FlowResult: """Handle a flow initialized by the user.""" - self._errors = {} - - # Uncomment the next 2 lines if only a single instance of the integration is allowed: - # if self._async_current_entries(): - # return self.async_abort(reason="single_instance_allowed") # noqa: E800 - + _errors = {} if user_input is not None: - valid = await self._test_credentials( - user_input[CONF_USERNAME], user_input[CONF_PASSWORD] - ) - if valid: + try: + await self._test_credentials( + username=user_input[CONF_USERNAME], + password=user_input[CONF_PASSWORD], + ) + except IntegrationBlueprintApiClientAuthenticationError as exception: + LOGGER.warning(exception) + _errors["base"] = "auth" + except IntegrationBlueprintApiClientCommunicationError as exception: + LOGGER.error(exception) + _errors["base"] = "connection" + except IntegrationBlueprintApiClientError as exception: + LOGGER.exception(exception) + _errors["base"] = "unknown" + else: return self.async_create_entry( - title=user_input[CONF_USERNAME], data=user_input + title=user_input[CONF_USERNAME], + data=user_input, ) - self._errors["base"] = "auth" - - user_input = {} - # Provide defaults for form - user_input[CONF_USERNAME] = "" - user_input[CONF_PASSWORD] = "" - - return await self._show_config_form(user_input) - - @staticmethod - @callback - def async_get_options_flow(config_entry: ConfigEntry): - """Get component options flow.""" - return BlueprintOptionsFlowHandler(config_entry) - - # pylint: disable=unused-argument - async def _show_config_form(self, user_input: Optional[ConfigType]): - """Show the configuration form to edit location data.""" - return self.async_show_form( - step_id="user", - data_schema=vol.Schema( - { - vol.Required(CONF_USERNAME, default=user_input[CONF_USERNAME]): str, - vol.Required(CONF_PASSWORD, default=user_input[CONF_PASSWORD]): str, - } - ), - errors=self._errors, - ) - - async def _test_credentials(self, username: str, password: str) -> bool: - """Return true if credentials is valid.""" - try: - session = async_create_clientsession(self.hass) - client = IntegrationBlueprintApiClient(username, password, session) - await client.async_get_data() - return True - - except Exception: # pylint: disable=broad-except - return False - - -class BlueprintOptionsFlowHandler(config_entries.OptionsFlow): - """Blueprint config flow options handler.""" - - def __init__(self, config_entry: ConfigEntry): - """Initialize Blueprint options flow.""" - self.config_entry = config_entry - self.options = dict(config_entry.options) - - # pylint: disable=unused-argument - async def async_step_init(self, user_input: Optional[ConfigType] = None): - """Manage the options.""" - return await self.async_step_user() - - async def async_step_user(self, user_input: Optional[ConfigType] = None): - """Handle a flow initialized by the user.""" - if user_input is not None: - self.options.update(user_input) - return await self._update_options() - return self.async_show_form( step_id="user", data_schema=vol.Schema( { - vol.Required(str(x), default=self.options.get(str(x), True)): bool - for x in sorted(PLATFORMS) + vol.Required( + CONF_USERNAME, + default=(user_input or {}).get(CONF_USERNAME), + ): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.TEXT + ), + ), + vol.Required(CONF_PASSWORD): selector.TextSelector( + selector.TextSelectorConfig( + type=selector.TextSelectorType.PASSWORD + ), + ), } ), + errors=_errors, ) - async def _update_options(self): - """Update config entry options.""" - return self.async_create_entry( - title=self.config_entry.data.get(CONF_USERNAME), data=self.options + async def _test_credentials(self, username: str, password: str) -> None: + """Validate credentials.""" + client = IntegrationBlueprintApiClient( + username=username, + password=password, + session=async_create_clientsession(self.hass), ) + await client.async_get_data() diff --git a/custom_components/integration_blueprint/const.py b/custom_components/integration_blueprint/const.py index 62bf75d..66c28f3 100644 --- a/custom_components/integration_blueprint/const.py +++ b/custom_components/integration_blueprint/const.py @@ -1,44 +1,9 @@ """Constants for integration_blueprint.""" +from logging import Logger, getLogger -from typing import Final +LOGGER: Logger = getLogger(__package__) -from homeassistant.const import Platform - -# Base component constants -NAME: Final = "Integration blueprint" -DOMAIN: Final = "integration_blueprint" -VERSION: Final = "0.1.0" -ATTRIBUTION: Final = "Data provided by http://jsonplaceholder.typicode.com/" -ISSUE_URL: Final = "https://github.com/Limych/ha-blueprint/issues" - -STARTUP_MESSAGE: Final = f""" -------------------------------------------------------------------- -{NAME} -Version: {VERSION} -This is a custom integration! -If you have ANY issues with this you need to open an issue here: -{ISSUE_URL} -------------------------------------------------------------------- -""" - -# Icons -ICON: Final = "mdi:format-quote-close" - -# Device classes -BINARY_SENSOR_DEVICE_CLASS: Final = "connectivity" - -# Platforms -PLATFORMS: Final = [ - Platform.BINARY_SENSOR, - Platform.SENSOR, - Platform.SWITCH, -] - -# Configuration and options -CONF_ENABLED: Final = "enabled" - -# Defaults -DEFAULT_NAME: Final = DOMAIN - -# Attributes -ATTR_INTEGRATION: Final = "integration" +NAME = "Integration blueprint" +DOMAIN = "integration_blueprint" +VERSION = "0.0.0" +ATTRIBUTION = "Data provided by http://jsonplaceholder.typicode.com/" diff --git a/custom_components/integration_blueprint/coordinator.py b/custom_components/integration_blueprint/coordinator.py new file mode 100644 index 0000000..d427a1a --- /dev/null +++ b/custom_components/integration_blueprint/coordinator.py @@ -0,0 +1,49 @@ +"""DataUpdateCoordinator for integration_blueprint.""" +from __future__ import annotations + +from datetime import timedelta + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import ( + DataUpdateCoordinator, + UpdateFailed, +) +from homeassistant.exceptions import ConfigEntryAuthFailed + +from .api import ( + IntegrationBlueprintApiClient, + IntegrationBlueprintApiClientAuthenticationError, + IntegrationBlueprintApiClientError, +) +from .const import DOMAIN, LOGGER + + +# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities +class BlueprintDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" + + config_entry: ConfigEntry + + def __init__( + self, + hass: HomeAssistant, + client: IntegrationBlueprintApiClient, + ) -> None: + """Initialize.""" + self.client = client + super().__init__( + hass=hass, + logger=LOGGER, + name=DOMAIN, + update_interval=timedelta(minutes=5), + ) + + async def _async_update_data(self): + """Update data via library.""" + try: + return await self.client.async_get_data() + except IntegrationBlueprintApiClientAuthenticationError as exception: + raise ConfigEntryAuthFailed(exception) from exception + except IntegrationBlueprintApiClientError as exception: + raise UpdateFailed(exception) from exception diff --git a/custom_components/integration_blueprint/entity.py b/custom_components/integration_blueprint/entity.py index 90fea43..4325227 100644 --- a/custom_components/integration_blueprint/entity.py +++ b/custom_components/integration_blueprint/entity.py @@ -1,38 +1,25 @@ """BlueprintEntity class.""" -from homeassistant.const import ATTR_ATTRIBUTION, ATTR_ID +from __future__ import annotations + +from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.update_coordinator import CoordinatorEntity -from .const import ATTR_INTEGRATION, ATTRIBUTION, DOMAIN, NAME, VERSION +from .const import ATTRIBUTION, DOMAIN, NAME, VERSION +from .coordinator import BlueprintDataUpdateCoordinator class IntegrationBlueprintEntity(CoordinatorEntity): - """Blueprint entity.""" - - def __init__(self, coordinator, config_entry): - """Class initialization.""" - super().__init__(coordinator) - self.config_entry = config_entry + """BlueprintEntity class.""" - @property - def unique_id(self): - """Return a unique ID to use for this entity.""" - return self.config_entry.entry_id + _attr_attribution = ATTRIBUTION - @property - def device_info(self): - """Return the device info.""" - return { - "identifiers": {(DOMAIN, self.unique_id)}, - "name": NAME, - "model": VERSION, - "manufacturer": NAME, - } - - @property - def extra_state_attributes(self): - """Return the state attributes.""" - return { - ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_ID: str(self.coordinator.data.get("id")), - ATTR_INTEGRATION: DOMAIN, - } + def __init__(self, coordinator: BlueprintDataUpdateCoordinator) -> None: + """Initialize.""" + super().__init__(coordinator) + self._attr_unique_id = coordinator.config_entry.entry_id + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, self.unique_id)}, + name=NAME, + model=VERSION, + manufacturer=NAME, + ) diff --git a/custom_components/integration_blueprint/manifest.json b/custom_components/integration_blueprint/manifest.json index 7fb51f4..623e7af 100644 --- a/custom_components/integration_blueprint/manifest.json +++ b/custom_components/integration_blueprint/manifest.json @@ -1,14 +1,16 @@ { - "domain": "integration_blueprint", - "name": "Integration blueprint", - "version": "0.1.0", - "documentation": "https://github.com/Limych/ha-blueprint", - "issue_tracker": "https://github.com/Limych/ha-blueprint/issues", - "dependencies": [], - "config_flow": true, + "domain": "", + "name": "", "codeowners": [ - "@Limych" + "@ludeeus" ], - "requirements": [], - "iot_class": "cloud_polling" + "config_flow": true, + "documentation": "https://github.com/ludeeus/integration_blueprint", + "iot_class": "cloud_polling", + "issue_tracker": "", + "version": "", + "requirements": [ + "colorlog==6.7.0", + "ruff==0.0.267" + ] } \ No newline at end of file diff --git a/custom_components/integration_blueprint/sensor.py b/custom_components/integration_blueprint/sensor.py index fb5c9a8..06201fe 100644 --- a/custom_components/integration_blueprint/sensor.py +++ b/custom_components/integration_blueprint/sensor.py @@ -1,32 +1,46 @@ """Sensor platform for integration_blueprint.""" -from homeassistant.components.sensor import SensorEntity -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from __future__ import annotations -from .const import DEFAULT_NAME, DOMAIN, ICON +from homeassistant.components.sensor import SensorEntity, SensorEntityDescription + +from .const import DOMAIN +from .coordinator import BlueprintDataUpdateCoordinator from .entity import IntegrationBlueprintEntity +ENTITY_DESCRIPTIONS = ( + SensorEntityDescription( + key="integration_blueprint", + name="Integration Sensor", + icon="mdi:format-quote-close", + ), +) + -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): - """Set up sensor platform.""" +async def async_setup_entry(hass, entry, async_add_devices): + """Set up the sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([IntegrationBlueprintSensor(coordinator, entry)]) + async_add_devices( + IntegrationBlueprintSensor( + coordinator=coordinator, + entity_description=entity_description, + ) + for entity_description in ENTITY_DESCRIPTIONS + ) class IntegrationBlueprintSensor(IntegrationBlueprintEntity, SensorEntity): """integration_blueprint Sensor class.""" - @property - def name(self): - """Return the name of the sensor.""" - return f"{DEFAULT_NAME}_{Platform.SENSOR}" + def __init__( + self, + coordinator: BlueprintDataUpdateCoordinator, + entity_description: SensorEntityDescription, + ) -> None: + """Initialize the sensor class.""" + super().__init__(coordinator) + self.entity_description = entity_description @property - def native_value(self): + def native_value(self) -> str: """Return the native value of the sensor.""" return self.coordinator.data.get("body") - - @property - def icon(self): - """Return the icon of the sensor.""" - return ICON diff --git a/custom_components/integration_blueprint/switch.py b/custom_components/integration_blueprint/switch.py index c24c254..33340a2 100644 --- a/custom_components/integration_blueprint/switch.py +++ b/custom_components/integration_blueprint/switch.py @@ -1,42 +1,56 @@ """Switch platform for integration_blueprint.""" -from homeassistant.components.switch import SwitchEntity -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant +from __future__ import annotations -from .const import DEFAULT_NAME, DOMAIN, ICON +from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription + +from .const import DOMAIN +from .coordinator import BlueprintDataUpdateCoordinator from .entity import IntegrationBlueprintEntity +ENTITY_DESCRIPTIONS = ( + SwitchEntityDescription( + key="integration_blueprint", + name="Integration Switch", + icon="mdi:format-quote-close", + ), +) + -async def async_setup_entry(hass: HomeAssistant, entry, async_add_entities): - """Set up sensor platform.""" +async def async_setup_entry(hass, entry, async_add_devices): + """Set up the sensor platform.""" coordinator = hass.data[DOMAIN][entry.entry_id] - async_add_entities([IntegrationBlueprintBinarySwitch(coordinator, entry)]) + async_add_devices( + IntegrationBlueprintSwitch( + coordinator=coordinator, + entity_description=entity_description, + ) + for entity_description in ENTITY_DESCRIPTIONS + ) -class IntegrationBlueprintBinarySwitch(IntegrationBlueprintEntity, SwitchEntity): +class IntegrationBlueprintSwitch(IntegrationBlueprintEntity, SwitchEntity): """integration_blueprint switch class.""" - async def async_turn_on(self, **kwargs): # pylint: disable=unused-argument + def __init__( + self, + coordinator: BlueprintDataUpdateCoordinator, + entity_description: SwitchEntityDescription, + ) -> None: + """Initialize the switch class.""" + super().__init__(coordinator) + self.entity_description = entity_description + + @property + def is_on(self) -> bool: + """Return true if the switch is on.""" + return self.coordinator.data.get("title", "") == "foo" + + async def async_turn_on(self, **_: any) -> None: """Turn on the switch.""" await self.coordinator.api.async_set_title("bar") await self.coordinator.async_request_refresh() - async def async_turn_off(self, **kwargs): # pylint: disable=unused-argument + async def async_turn_off(self, **_: any) -> None: """Turn off the switch.""" await self.coordinator.api.async_set_title("foo") await self.coordinator.async_request_refresh() - - @property - def name(self): - """Return the name of the switch.""" - return f"{DEFAULT_NAME}_{Platform.SWITCH}" - - @property - def icon(self): - """Return the icon of this switch.""" - return ICON - - @property - def is_on(self): - """Return true if the switch is on.""" - return self.coordinator.data.get("title", "") == "foo" diff --git a/custom_components/integration_blueprint/translations/en.json b/custom_components/integration_blueprint/translations/en.json index e366e14..049f7a4 100644 --- a/custom_components/integration_blueprint/translations/en.json +++ b/custom_components/integration_blueprint/translations/en.json @@ -2,8 +2,7 @@ "config": { "step": { "user": { - "title": "Blueprint", - "description": "If you need help with the configuration have a look here: https://github.com/Limych/ha-blueprint", + "description": "If you need help with the configuration have a look here: https://github.com/ludeeus/integration_blueprint", "data": { "username": "Username", "password": "Password" @@ -11,21 +10,9 @@ } }, "error": { - "auth": "Username/Password is wrong." - }, - "abort": { - "single_instance_allowed": "Only a single instance is allowed." - } - }, - "options": { - "step": { - "user": { - "data": { - "binary_sensor": "Binary sensor enabled", - "sensor": "Sensor enabled", - "switch": "Switch enabled" - } - } + "auth": "Username/Password is wrong.", + "connection": "Unable to connect to the server.", + "unknown": "Unknown error occurred." } } -} +} \ No newline at end of file diff --git a/custom_components/integration_blueprint/translations/fr.json b/custom_components/integration_blueprint/translations/fr.json deleted file mode 100644 index fc7f1ff..0000000 --- a/custom_components/integration_blueprint/translations/fr.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Blueprint", - "description": "Si vous avez besoin d'aide pour la configuration, regardez ici: https://github.com/Limych/ha-blueprint", - "data": { - "username": "Identifiant", - "password": "Mot de Passe" - } - } - }, - "error": { - "auth": "Identifiant ou mot de passe erroné." - }, - "abort": { - "single_instance_allowed": "Une seule instance est autorisée." - } - }, - "options": { - "step": { - "user": { - "data": { - "binary_sensor": "Capteur binaire activé", - "sensor": "Capteur activé", - "switch": "Interrupteur activé" - } - } - } - } -} diff --git a/custom_components/integration_blueprint/translations/nb.json b/custom_components/integration_blueprint/translations/nb.json deleted file mode 100644 index 7447ea1..0000000 --- a/custom_components/integration_blueprint/translations/nb.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "config": { - "step": { - "user": { - "title": "Blueprint", - "description": "Hvis du trenger hjep til konfigurasjon ta en titt her: https://github.com/Limych/ha-blueprint", - "data": { - "username": "Brukernavn", - "password": "Passord" - } - } - }, - "error": { - "auth": "Brukernavn/Passord er feil." - }, - "abort": { - "single_instance_allowed": "Denne integrasjonen kan kun konfigureres en gang." - } - }, - "options": { - "step": { - "user": { - "data": { - "binary_sensor": "Binær sensor aktivert", - "sensor": "Sensor aktivert", - "switch": "Bryter aktivert" - } - } - } - } -} diff --git a/example.png b/example.png deleted file mode 100644 index c2d4244..0000000 Binary files a/example.png and /dev/null differ diff --git a/hacs.json b/hacs.json index 258691a..3d3f4bf 100644 --- a/hacs.json +++ b/hacs.json @@ -1,5 +1,8 @@ { - "name": "Integration blueprint", - "hacs": "1.6.0", - "homeassistant": "2022.10.0" + "name": "", + "filename": "integration_blueprint.zip", + "hide_default_branch": true, + "homeassistant": "2023.2.0", + "render_readme": true, + "zip_release": true } diff --git a/requirements.txt b/requirements.txt index 0ab7865..4bfb7c6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,4 @@ -homeassistant>=2022.10.0 +colorlog==6.7.0 +homeassistant>=2023.2.0 +pip>=21.0,<23.2 +ruff==0.0.267 diff --git a/bin/_common b/scripts/_common similarity index 100% rename from bin/_common rename to scripts/_common diff --git a/bin/check_dirty b/scripts/check_dirty similarity index 100% rename from bin/check_dirty rename to scripts/check_dirty diff --git a/bin/devcontainer b/scripts/devcontainer similarity index 99% rename from bin/devcontainer rename to scripts/devcontainer index c931c82..c7f7791 100755 --- a/bin/devcontainer +++ b/scripts/devcontainer @@ -7,7 +7,7 @@ ROOT="$( cd "$( dirname "$(readlink -f "$0")" )/.." >/dev/null 2>&1 && pwd )" cd "${ROOT}" # Load common functions -source ./bin/_common +source ./scripts/_common docker=$(which docker) || die "ERROR: Docker not found." diff --git a/scripts/develop b/scripts/develop new file mode 100755 index 0000000..89eda50 --- /dev/null +++ b/scripts/develop @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +# Create config dir if not present +if [[ ! -d "${PWD}/config" ]]; then + mkdir -p "${PWD}/config" + hass --config "${PWD}/config" --script ensure_config +fi + +# Set the path to custom_components +## This let's us have the structure we want /custom_components/integration_blueprint +## while at the same time have Home Assistant configuration inside /config +## without resulting to symlinks. +export PYTHONPATH="${PYTHONPATH}:${PWD}/custom_components" + +# Start Home Assistant +hass --config "${PWD}/config" --debug diff --git a/bin/gen_releasenotes b/scripts/gen_releasenotes similarity index 100% rename from bin/gen_releasenotes rename to scripts/gen_releasenotes diff --git a/bin/install_requirements b/scripts/install_requirements similarity index 100% rename from bin/install_requirements rename to scripts/install_requirements diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..9b5b1df --- /dev/null +++ b/scripts/lint @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +ruff check . --fix diff --git a/bin/release b/scripts/release similarity index 97% rename from bin/release rename to scripts/release index 8e82186..58b7602 100755 --- a/bin/release +++ b/scripts/release @@ -7,7 +7,7 @@ ROOT="$( cd "$( dirname "$(readlink -f "$0")" )/.." >/dev/null 2>&1 && pwd )" cd "${ROOT}" # Load common functions -source ./bin/_common +source ./scripts/_common NAT='0|[1-9][0-9]*' @@ -109,7 +109,7 @@ new=$(validate_version "${1}") log.info "Patch files to version '${new}'..." sed -i -E "s/(^VERSION: Final = \")[^\"]*/\\1${new}/" ${const_path} -./bin/update_manifest +./scripts/update_manifest if output=$(git status --porcelain) && [[ -n "$output" ]]; then git commit -a --no-verify -m "Bump version to ${new}" fi @@ -124,4 +124,4 @@ fi log.info "Patch files to version '${dev}'..." sed -i -E "s/(^VERSION: Final = \")[^\"]*/\\1${dev}/" ${const_path} -./bin/update_manifest +./scripts/update_manifest diff --git a/bin/run-in-env b/scripts/run-in-env similarity index 100% rename from bin/run-in-env rename to scripts/run-in-env diff --git a/bin/setup b/scripts/setup similarity index 98% rename from bin/setup rename to scripts/setup index 4670f55..650a348 100755 --- a/bin/setup +++ b/scripts/setup @@ -10,7 +10,7 @@ cd "${ROOT}" python=$(which python3) # Load common functions -source ./bin/_common +source ./scripts/_common if [ ! -d "venv" ]; then log.info "Initializing the virtual environment..." diff --git a/bin/update b/scripts/update similarity index 100% rename from bin/update rename to scripts/update diff --git a/bin/update_manifest b/scripts/update_manifest similarity index 96% rename from bin/update_manifest rename to scripts/update_manifest index 0e8f340..52ffeca 100755 --- a/bin/update_manifest +++ b/scripts/update_manifest @@ -8,7 +8,7 @@ ROOT="$( cd "$( dirname "$(readlink -f "$0")" )/.." >/dev/null 2>&1 && pwd )" cd "${ROOT}" # Load common functions -source ./bin/_common +source ./scripts/_common if [[ $(ls -q "${ROOT}/custom_components/" | grep -v __ | wc -l) > 1 ]]; then log.error "WARNING: Detected more than one custom component. This script will update only first one of them." @@ -31,7 +31,7 @@ sed -i -E "s!(\"domain\": \")[^\"]*!\\1${domain}!" ${manifest_path} sed -i -E "s!(\"version\": \")[^\"]*!\\1${version}!" ${manifest_path} sed -i -E "s!(\"issue_tracker\": \")[^\"]*!\\1${issue_url}!" ${manifest_path} -bin/update_requirements +./scripts/update_requirements log.info "Update hacs.json data..." hacs_path="${ROOT}/hacs.json" diff --git a/bin/update_requirements b/scripts/update_requirements similarity index 100% rename from bin/update_requirements rename to scripts/update_requirements