diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..16170ff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,134 @@ +name: CI + +# yamllint disable-line rule:truthy +on: + #push: + #pull_request: ~ + workflow_dispatch: + +env: + CACHE_VERSION: 1 + PYTHON_VERSION_DEFAULT: '3.10.8' + PRE_COMMIT_HOME: ~/.cache/pre-commit + +jobs: + # Separate job to pre-populate the base dependency cache + # This prevent upcoming jobs to do the same individually + prepare-base: + name: Prepare base dependencies + runs-on: ubuntu-latest + strategy: + matrix: + #python-version: ['3.8.14', '3.9.15', '3.10.8', '3.11.0'] + python-version: ['3.10.8', '3.11.0'] + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3 + - name: Set up Python ${{ matrix.python-version }} + id: python + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache@v3 + with: + path: venv + key: >- + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ + steps.python.outputs.python-version }}-${{ + hashFiles('setup.py', 'requirements_test.txt') }} + restore-keys: | + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ steps.python.outputs.python-version }}- + - name: Create Python virtual environment + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + python -m venv venv + . venv/bin/activate + pip install -U pip setuptools pre-commit + pip install -r requirements_test.txt + pip install -e . + + pre-commit: + name: Prepare pre-commit environment + runs-on: ubuntu-latest + needs: prepare-base + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3 + - name: Set up Python ${{ env.PYTHON_VERSION_DEFAULT }} + uses: actions/setup-python@v4 + id: python + with: + python-version: ${{ env.PYTHON_VERSION_DEFAULT }} + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache@v3 + with: + path: venv + key: >- + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ + steps.python.outputs.python-version }}-${{ + hashFiles('setup.py', 'requirements_test.txt') }} + - name: Fail job if Python cache restore failed + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + echo "Failed to restore Python virtual environment from cache" + exit 1 + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache@v3 + with: + path: ${{ env.PRE_COMMIT_HOME }} + key: | + ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + restore-keys: | + ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit- + - name: Install pre-commit dependencies + if: steps.cache-precommit.outputs.cache-hit != 'true' + run: | + . venv/bin/activate + pre-commit install-hooks + + pre-commit-run: + name: Run all of pre-commit + runs-on: ubuntu-latest + needs: pre-commit + steps: + - name: Check out code from GitHub + uses: actions/checkout@v3 + - name: Set up Python ${{ env.PYTHON_VERSION_DEFAULT }} + uses: actions/setup-python@v4 + id: python + with: + python-version: ${{ env.PYTHON_VERSION_DEFAULT }} + - name: Restore base Python virtual environment + id: cache-venv + uses: actions/cache@v3 + with: + path: venv + key: >- + ${{ env.CACHE_VERSION}}-${{ runner.os }}-base-venv-${{ + steps.python.outputs.python-version }}-${{ + hashFiles('setup.py', 'requirements_test.txt') }} + - name: Fail job if Python cache restore failed + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + echo "Failed to restore Python virtual environment from cache" + exit 1 + - name: Restore pre-commit environment from cache + id: cache-precommit + uses: actions/cache@v3 + with: + path: ${{ env.PRE_COMMIT_HOME }} + key: | + ${{ env.CACHE_VERSION}}-${{ runner.os }}-pre-commit-${{ hashFiles('.pre-commit-config.yaml') }} + - name: Fail job if cache restore failed + if: steps.cache-venv.outputs.cache-hit != 'true' + run: | + echo "Failed to restore Python virtual environment from cache" + exit 1 + - name: Run pre-commit + run: | + . venv/bin/activate + pre-commit run -a diff --git a/.github/workflows/validate-hacs.yml b/.github/workflows/validate-hacs.yml new file mode 100644 index 0000000..a7540c1 --- /dev/null +++ b/.github/workflows/validate-hacs.yml @@ -0,0 +1,24 @@ +--- +name: Validate with hassfest + +on: + push: + pull_request: + schedule: + - cron: 0 0 * * * + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: home-assistant/actions/hassfest@master + hacs: + name: HACS Action + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: HACS Action + uses: hacs/action@main + with: + category: integration diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..f34537c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,135 @@ +--- +files: ^(.*\.(py|json|md|sh|yaml|cfg|txt))$ +exclude: ^(\.[^/]*cache/.*|.*/_user.py)$ +repos: + - repo: https://github.com/verhovsky/pyupgrade-docs + rev: v0.3.0 + hooks: + - id: pyupgrade-docs + - repo: https://github.com/executablebooks/mdformat + # Do this before other tools "fixing" the line endings + rev: 0.7.16 + hooks: + - id: mdformat + name: Format Markdown + entry: mdformat # Executable to run, with fixed options + language: python + types: [markdown] + args: [--wrap, '75', --number] + additional_dependencies: + - mdformat-toc + - mdformat-beautysh + # -mdformat-shfmt + # -mdformat-tables + - mdformat-config + - mdformat-black + - mdformat-web + - mdformat-gfm + - repo: https://github.com/asottile/blacken-docs + rev: 1.13.0 + hooks: + - id: blacken-docs + additional_dependencies: [black==22.6.0] + stages: [manual] # Manual because already done by mdformat-black + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: no-commit-to-branch + args: [--branch, main] + - id: check-yaml + args: [--unsafe] + - id: debug-statements + - id: end-of-file-fixer + - id: trailing-whitespace + - id: check-json + - id: mixed-line-ending + - id: check-builtin-literals + - id: check-ast + - id: check-merge-conflict + - id: check-executables-have-shebangs + - id: check-shebang-scripts-are-executable + - id: check-docstring-first + - id: fix-byte-order-marker + - id: check-case-conflict + # - id: check-toml + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.29.0 + hooks: + - id: yamllint + args: + - --no-warnings + - -d + - '{extends: relaxed, rules: {line-length: {max: 90}}}' + - repo: https://github.com/lovesegfault/beautysh.git + rev: v6.2.1 + hooks: + - id: beautysh + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: + - --py310-plus + - repo: https://github.com/psf/black + rev: 22.12.0 + hooks: + - id: black + args: + - --safe + - --quiet + - -l 79 + - repo: https://github.com/Lucas-C/pre-commit-hooks-bandit + rev: v1.0.6 + hooks: + - id: python-bandit-vulnerability-check + - repo: https://github.com/fsouza/autoflake8 + rev: v0.4.0 + hooks: + - id: autoflake8 + args: + - -i + - -r + - --expand-star-imports + - custom_components + - repo: https://github.com/PyCQA/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + additional_dependencies: + # - pyproject-flake8>=0.0.1a5 + - flake8-bugbear>=22.7.1 + - flake8-comprehensions>=3.10.1 + - flake8-2020>=1.7.0 + - mccabe>=0.7.0 + - pycodestyle>=2.9.1 + - pyflakes>=2.5.0 + - repo: https://github.com/PyCQA/isort + rev: 5.12.0 + hooks: + - id: isort + - repo: https://github.com/codespell-project/codespell + rev: v2.2.2 + hooks: + - id: codespell + args: [--toml, pyproject.toml] + additional_dependencies: + - tomli + - repo: https://github.com/pre-commit/mirrors-pylint + rev: v3.0.0a5 + hooks: + - id: pylint + additional_dependencies: + #- voluptuous==0.13.1 + - homeassistant-stubs==2023.3.1 + #- sqlalchemy + #- pyyaml + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v0.991 + hooks: + - id: mypy + additional_dependencies: + #- voluptuous==0.13.1 + - pydantic>=1.10.5 + - homeassistant-stubs==2023.3.1 + #- sqlalchemy + #- pyyaml diff --git a/custom_components/programmable_thermostat/climate.py b/custom_components/programmable_thermostat/climate.py index 5de8126..106934a 100755 --- a/custom_components/programmable_thermostat/climate.py +++ b/custom_components/programmable_thermostat/climate.py @@ -69,7 +69,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Add ProgrammableThermostat entities from configuration.yaml.""" _LOGGER.info("Setup entity coming from configuration.yaml named: %s", config.get(CONF_NAME)) - await async_setup_reload_service(hass, DOMAIN, PLATFORM) + await async_setup_reload_service(hass, DOMAIN, [PLATFORM]) async_add_entities([ProgrammableThermostat(hass, config)]) async def async_setup_entry(hass, config_entry, async_add_devices): @@ -80,7 +80,7 @@ async def async_setup_entry(hass, config_entry, async_add_devices): else: result = config_entry.data _LOGGER.info("setup entity-config_entry_data=%s",result) - await async_setup_reload_service(hass, DOMAIN, PLATFORM) + await async_setup_reload_service(hass, DOMAIN, [PLATFORM]) async_add_devices([ProgrammableThermostat(hass, result)]) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7efa09d --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,102 @@ +[project] +# PEP 621 project metadata +# See https://www.python.org/dev/peps/pep-0621/ +dynamic = ["version"] +#authors = [ {name = "TBD", email = "TBD"}, ] +#license = {text = "TBD"} +requires-python = ">=3.9.0" +dependencies = [ +] +name = "ha_programmable_thermostat" +description = "TBD" +readme = "README.md" +classifiers=[ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + #"License :: OSI Approved :: TBD", + "Operating System :: Unix", + "Operating System :: POSIX", + "Programming Language :: Python", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Topic :: Utilities", + "Natural Language :: English", +] + + +[tool.codespell] +ignore-words-list = """ +master, +slave, +hass""" +skip = """ +./.*,./assets/*,./data/*,*.svg,*.css,*.json,*.js +""" +quiet-level=2 +ignore-regex = '\\[fnrstv]' +builtin = "clear,rare,informal,usage,code,names" + +# --------- Pylint ------------- +[tool.pylint.'TYPECHECK'] +generated-members = "sh" + +[tool.pylint.'MESSAGES CONTROL'] +extension-pkg-whitelist = "pydantic" +disable = [ + "broad-except", + "invalid-name", + "line-too-long", + "missing-function-docstring", + "missing-module-docstring", + "too-few-public-methods", + "too-many-arguments", + "too-many-branches", + "too-many-instance-attributes", + "too-many-statements", + "unused-import", + "wrong-import-order", +] + +[tool.pylint.FORMAT] +expected-line-ending-format = "LF" + + + +# --------- Mypy ------------- + +[tool.mypy] +show_error_codes = true +follow_imports = "silent" +ignore_missing_imports = false +strict_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +disallow_any_generics = true +check_untyped_defs = true +no_implicit_reexport = true +warn_unused_configs = true +disallow_subclassing_any = true +disallow_incomplete_defs = true +disallow_untyped_decorators = true +disallow_untyped_calls = true +disallow_untyped_defs = true +plugins = [ + "pydantic.mypy" +] + +[tool.pydantic-mypy] +init_forbid_extra = true +init_typed = true +warn_required_dynamic_aliases = true +warn_untyped_fields = true + +[[tool.mypy.overrides]] +module = "tests.*" +# Required to not have error: Untyped decorator makes function on fixtures and +# parametrize decorators +disallow_untyped_decorators = false + +[[tool.mypy.overrides]] +#module = [ ] +ignore_missing_imports = true diff --git a/requirements_test.txt b/requirements_test.txt new file mode 100644 index 0000000..e69de29