diff --git a/.devcontainer.json b/.devcontainer.json new file mode 100644 index 0000000..7b539a1 --- /dev/null +++ b/.devcontainer.json @@ -0,0 +1,43 @@ +{ + "name": "ludeeus/integration_blueprint", + "image": "mcr.microsoft.com/vscode/devcontainers/python:0-3.11-bullseye", + "postCreateCommand": "scripts/setup", + "appPort": [ + "9124:8123" + ], + "portsAttributes": { + "8123": { + "label": "Home Assistant - Energi Data Service", + "onAutoForward": "notify" + } + }, + "customizations": { + "vscode": { + "extensions": [ + "ms-python.python", + "github.vscode-pull-request-github", + "ryanluker.vscode-coverage-gutters", + "ms-python.vscode-pylance", + "ms-python.black-formatter" + ], + "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": "root", + "features": { + "ghcr.io/devcontainers/features/rust:1": {} + } +} \ No newline at end of file diff --git a/.devcontainer/README.md b/.devcontainer/README.md deleted file mode 100644 index ab32b6d..0000000 --- a/.devcontainer/README.md +++ /dev/null @@ -1,69 +0,0 @@ -## 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/). - -### Workaround for creating new devcontainer - -As seen in [this issue](https://github.com/custom-components/integration_blueprint/issues/71) there is an error creating a new devcontainer. -The current workaround is confirmed working: - -1. `nano /usr/share/container/upgrade` -2. replace `python3 -m pip install --upgrade git+git://github.com/home-assistant/home-assistant.git@dev` with `python3 -m pip install --upgrade git+https://github.com/home-assistant/home-assistant.git@dev`, save changes -3. run `container install` \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 24b5564..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": "Energidataservice custom component development", - "context": "..", - "appPort": [ - "9124: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 - } -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index f712391..98417a3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,18 @@ +# artifacts __pycache__ -pythonenv* -venv -.venv +.pytest* +*.egg-info +*/build/* +*/dist/* + + +# misc .coverage -.idea -.storage +.vscode +coverage.xml notes.txt + + +# Home Assistant configuration +config/* +!config/configuration.yaml \ No newline at end of file diff --git a/.ruff.toml b/.ruff.toml new file mode 100644 index 0000000..65bb225 --- /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 = "py311" + +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/settings.json b/.vscode/settings.json index f7f6619..a2a60ec 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,12 +1,16 @@ { "python.linting.pylintEnabled": true, "python.linting.enabled": true, - "python.pythonPath": "/usr/local/bin/python", "files.associations": { "*.yaml": "home-assistant" }, "[python]": { - "editor.defaultFormatter": "ms-python.black-formatter" - }, - "python.formatting.provider": "none" + "editor.defaultFormatter": "ms-python.black-formatter", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": true + }, + }, + "isort.args":["--profile", "black"], + "python.formatting.provider": "black" } \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 74a24e6..6ccd283 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,27 +2,9 @@ "version": "2.0.0", "tasks": [ { - "label": "Run Home Assistant on port 9124", + "label": "Run Home Assistant developer env on port 9124", "type": "shell", - "command": "container start", - "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", - "problemMatcher": [] - }, - { - "label": "Upgrade Python version", - "type": "shell", - "command": "cd ${workspaceFolder} && scripts/python/update", + "command": "scripts/develop", "problemMatcher": [] } ] diff --git a/.devcontainer/configuration.yaml b/config/configuration.yaml similarity index 100% rename from .devcontainer/configuration.yaml rename to config/configuration.yaml diff --git a/requirements.txt b/requirements.txt index dbbd1d6..d1bae43 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,6 @@ async-retrying==0.2.2 CurrencyConverter==0.17.9 +colorlog==6.7.0 +homeassistant==2023.8.2 +pip>=21.0,<23.3 +ruff==0.0.284 \ No newline at end of file diff --git a/requirements_dev.txt b/requirements_dev.txt deleted file mode 100644 index 6111067..0000000 --- a/requirements_dev.txt +++ /dev/null @@ -1,4 +0,0 @@ -requests -awesomeversion -pylint -black \ No newline at end of file diff --git a/scripts/develop b/scripts/develop new file mode 100755 index 0000000..20366e8 --- /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 \ No newline at end of file diff --git a/scripts/lint b/scripts/lint new file mode 100755 index 0000000..752d23a --- /dev/null +++ b/scripts/lint @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +ruff check . --fix \ No newline at end of file diff --git a/scripts/python/source_check.py b/scripts/python/source_check.py deleted file mode 100644 index d767841..0000000 --- a/scripts/python/source_check.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Update python.""" -import sys - -import requests -from version import Version - -URL = "https://www.python.org/downloads" - -curr_ver = (sys.version).split() -current = Version(curr_ver[0]) - -request = requests.get(URL, timeout=120).text -upstream = Version(request.split(">Download Python ")[2].split("<")[0]) - -if current != upstream: - print(upstream) diff --git a/scripts/python/update b/scripts/python/update deleted file mode 100755 index 2b19516..0000000 --- a/scripts/python/update +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash -set -e -echo -e "\\033[0;34mRunning Python update script\\033[0m" -echo -e "\\033[0;34mChecking for newer Python version\\033[0m" - -VERSION=`python3 scripts/python/source_check.py` -if [ ! -z $VERSION ] -then - echo -e "\\033[0;34mNewer Python version was found, continuing update process\\033[0m" - - echo -e "\\033[0;34mInstalling build dependencies\\033[0m" - apt-get update > /dev/null 2>&1 - apt-get -y install --no-install-recommends \ - curl \ - ca-certificates \ - tar \ - make \ - build-essential \ - libffi-dev \ - libssl-dev \ - zlib1g-dev \ - libbz2-dev \ - libreadline-dev \ - libsqlite3-dev \ - wget \ - jq \ - llvm \ - libncurses5-dev \ - libncursesw5-dev \ - xz-utils \ - tk-dev \ - liblzma-dev > /dev/null 2>&1 - - INSTALL_PATH="/usr/local/python" - - echo -en "\\033[0;34mInstalling Python\\033[0m " - echo -en "\\033[1;33m${VERSION}\\033[0m" - echo -en "\\033[0;34m to \\033[0m" - echo -e "\\033[1;33m${INSTALL_PATH}\\033[0m" - - echo -e "\\033[0;34mDownloading Python source\\033[0m" - mkdir -p /tmp/python-src "${INSTALL_PATH}" > /dev/null 2>&1 - curl -sSLf -o /tmp/python.tar.xz "https://www.python.org/ftp/python/${VERSION}/Python-${VERSION}.tar.xz" > /dev/null - tar -xJC "/tmp/python-src" --strip-components=1 -f /tmp/python.tar.xz > /dev/null 2>&1 - - echo -e "\\033[0;34mBuilding Python (this could take a long time)\\033[0m" - cd /tmp/python-src || exit 1 - ./configure \ - --prefix="${INSTALL_PATH}" \ - --enable-optimizations \ - --with-ensurepip=install > /dev/null 2>&1 - - make -j"$(nproc)" EXTRA_CFLAGS="-DTHREAD_STACK_SIZE=0x100000" > /dev/null 2>&1 - make install > /dev/null 2>&1 - - cd "${INSTALL_PATH}/bin" || exit 1 - ln -sf idle3 idle - ln -sf pydoc3 pydoc - ln -sf python3 python - ln -sf pip3 pip - ln -sf python3-config python-config - - rm -rf /tmp/python-dl.tgz /tmp/python-src > /dev/null 2>&1 - - echo -e "\\033[0;34mDone installing Python\\033[0m" - - echo -en "\\033[0;34mPython version:\\033[0m " - python --version - - echo -en "\\033[0;34mPython3 version:\\033[0m " - python3 --version - - echo -e "\\033[0;34mCleaning up\\033[0m" - - find /usr/local \( -type d -a -name test -o -name tests -o -name '__pycache__' \) -o \( -type f -a -name '*.pyc' -o -name '*.pyo' \) -exec rm -rf '{}' \; > /dev/null 2>&1 - - echo -e "\\033[0;34mReinstalling Home Assistant latest dev version\\033[0m" - - python3 -m pip --disable-pip-version-check install --upgrade git+https://github.com/home-assistant/home-assistant.git@dev > /dev/null 2>&1 - - echo -e "\\033[0;34mInstall integration requirements\\033[0m" - - python3 -m pip --disable-pip-version-check install --requirement requirements_dev.txt > /dev/null 2>&1 - - echo -e "\\033[0;34mUpgrading Python succeeded\\033[0m" -else - echo -e "\\033[0;34mPython version is up-to-date\\033[0m" -fi diff --git a/scripts/python/version.py b/scripts/python/version.py deleted file mode 100644 index a7c617a..0000000 --- a/scripts/python/version.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Version handler.""" -from awesomeversion import AwesomeVersion - - -class Version(AwesomeVersion): - @property - def tags(self): - tags = ["latest"] - tags.append(str(self.section(0))) - if self.sections > 1: - tags.append(f"{self.section(0)}.{self.section(1)}") - if self.sections > 2: - tags.append(f"{self.section(0)}.{self.section(1)}.{self.section(2)}") - return tags diff --git a/scripts/setup b/scripts/setup new file mode 100755 index 0000000..abe537a --- /dev/null +++ b/scripts/setup @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +cd "$(dirname "$0")/.." + +python3 -m pip install --requirement requirements.txt \ No newline at end of file diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 5102ced..0000000 --- a/setup.cfg +++ /dev/null @@ -1,35 +0,0 @@ -[flake8] -exclude = .venv,.git,.tox,docs,venv,bin,lib,deps,build -doctests = True -# To work with Black -max-line-length = 88 -# E501: line too long -# W503: Line break occurred before a binary operator -# E203: Whitespace before ':' -# D202 No blank lines allowed after function docstring -# W504 line break after binary operator -ignore = - E501, - W503, - E203, - D202, - W504 - -[isort] -# https://github.com/timothycrosley/isort -# https://github.com/timothycrosley/isort/wiki/isort-Settings -# splits long import on multiple lines indented by 4 spaces -multi_line_output = 3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 -indent = " " -# by default isort don't check module indexes -not_skip = __init__.py -# will group `import x` and `from x import` of the same module. -force_sort_within_sections = true -sections = FUTURE,STDLIB,INBETWEENS,THIRDPARTY,FIRSTPARTY,LOCALFOLDER -default_section = THIRDPARTY -known_first_party = custom_components.energidataservice -combine_as_imports = true diff --git a/test_dataset/20220325.json b/test_dataset/20220325.json deleted file mode 100644 index 5cf0cd5..0000000 --- a/test_dataset/20220325.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "data": { - "elspotprices": [ - { - "HourUTC": "2022-03-25T00:00:00+00:00", - "SpotPriceEUR": 242 - }, - { - "HourUTC": "2022-03-25T01:00:00+00:00", - "SpotPriceEUR": 234.29 - }, - { - "HourUTC": "2022-03-25T02:00:00+00:00", - "SpotPriceEUR": 230.15 - }, - { - "HourUTC": "2022-03-25T03:00:00+00:00", - "SpotPriceEUR": 226.01 - }, - { - "HourUTC": "2022-03-25T04:00:00+00:00", - "SpotPriceEUR": 254.91 - }, - { - "HourUTC": "2022-03-25T05:00:00+00:00", - "SpotPriceEUR": 315 - }, - { - "HourUTC": "2022-03-25T06:00:00+00:00", - "SpotPriceEUR": 333.86 - }, - { - "HourUTC": "2022-03-25T07:00:00+00:00", - "SpotPriceEUR": 240 - }, - { - "HourUTC": "2022-03-25T08:00:00+00:00", - "SpotPriceEUR": 220.1 - }, - { - "HourUTC": "2022-03-25T09:00:00+00:00", - "SpotPriceEUR": 185.6 - }, - { - "HourUTC": "2022-03-25T10:00:00+00:00", - "SpotPriceEUR": 185.87 - }, - { - "HourUTC": "2022-03-25T11:00:00+00:00", - "SpotPriceEUR": 172.57 - }, - { - "HourUTC": "2022-03-25T12:00:00+00:00", - "SpotPriceEUR": 169.75 - }, - { - "HourUTC": "2022-03-25T13:00:00+00:00", - "SpotPriceEUR": 178.57 - }, - { - "HourUTC": "2022-03-25T14:00:00+00:00", - "SpotPriceEUR": 199.11 - }, - { - "HourUTC": "2022-03-25T15:00:00+00:00", - "SpotPriceEUR": 193.13 - }, - { - "HourUTC": "2022-03-25T16:00:00+00:00", - "SpotPriceEUR": 194.19 - }, - { - "HourUTC": "2022-03-25T17:00:00+00:00", - "SpotPriceEUR": 196.52 - }, - { - "HourUTC": "2022-03-25T18:00:00+00:00", - "SpotPriceEUR": 197.22 - }, - { - "HourUTC": "2022-03-25T19:00:00+00:00", - "SpotPriceEUR": 193.35 - }, - { - "HourUTC": "2022-03-25T20:00:00+00:00", - "SpotPriceEUR": 193.1 - }, - { - "HourUTC": "2022-03-25T21:00:00+00:00", - "SpotPriceEUR": 192.55 - }, - { - "HourUTC": "2022-03-25T22:00:00+00:00", - "SpotPriceEUR": 180.88 - }, - { - "HourUTC": "2022-03-25T23:00:00+00:00", - "SpotPriceEUR": 164.35 - } - ] - } -} \ No newline at end of file