Browse Source

drop compatibility with python3.5

https://github.com/fphammerle/wireless-sensor/commit/70812edc7c30fc1d48772265c912ead5136b6e60
https://github.com/fphammerle/wireless-sensor/commit/c9a0056c79d0b505f8f588912b36bb91874e0ddd
https://github.com/fphammerle/ical2vdir/commit/fb9acddb0c37db254596d9aba0b78fb6ba548b8e
https://github.com/fphammerle/python-manchester-code/commit/71fbee60e2d367643466e697be8715cce401fcd0
https://github.com/fphammerle/freesurfer-surface/commit/378ea44397ed2c7575160d350abde6a859fa2336
https://github.com/fphammerle/freesurfer-volume-reader/commit/c1511cdbec8145aee119a56f847ce2b44e8f9400
https://github.com/fphammerle/wireless-sensor-mqtt/commit/f5afa1ef508e5f8fb5907b34a940696fc080a6a8
Fabian Peter Hammerle 2 years ago
parent
commit
fcb34cee5e

+ 1 - 1
.github/dependabot.yml

@@ -7,7 +7,7 @@ updates:
   # > - pipenv, pip-compile, and poetry (specify pip)
   package-ecosystem: pip
   directory: /
-  # avoid changes in Pipfile breaking python3.5 compatibility
+  # avoid changes in Pipfile breaking constraints for compatibility with older python versions
   versioning-strategy: lockfile-only
   schedule:
     interval: weekly

+ 10 - 13
.github/workflows/python.yml

@@ -14,11 +14,10 @@ on:
 
 jobs:
   code-format:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     strategy:
       matrix:
-        python-version:
-        - 3.8
+        python-verison: ['3.9']
     steps:
     - uses: actions/checkout@v2.3.4
     - uses: actions/setup-python@v2.2.2
@@ -31,15 +30,14 @@ jobs:
     - run: pipenv graph
     - run: pipenv run black --check .
   tests:
-    runs-on: ubuntu-18.04
+    runs-on: ubuntu-20.04
     strategy:
       matrix:
         python-version:
-        - 3.5
-        - 3.6
-        - 3.7
-        - 3.8
-        - 3.9
+        - '3.6'
+        - '3.7'
+        - '3.8'
+        - '3.9'
       fail-fast: false
     steps:
     - uses: actions/checkout@v2.3.4
@@ -47,14 +45,13 @@ jobs:
       with:
         python-version: ${{ matrix.python-version }}
     - run: pip install --upgrade pipenv==2020.8.13
-    - run: pipenv install --python "$PYTHON_VERSION" --deploy --dev
+    # by default pipenv picks the latest version in PATH
+    - run: pipenv --python "$PYTHON_VERSION" install --deploy --dev
       env:
         PYTHON_VERSION: ${{ matrix.python-version }}
     - run: pipenv graph
     - run: pipenv run pytest --cov="$(cat *.egg-info/top_level.txt)" --cov-report=term-missing --cov-fail-under=100
-    # https://github.com/PyCQA/pylint/issues/3882
-    - run: python3 -c 'import sys; sys.exit(sys.version_info < (3, 9))'
-        || pipenv run pylint --load-plugins=pylint_import_requirements "$(cat *.egg-info/top_level.txt)"
+    - run: pipenv run pylint --load-plugins=pylint_import_requirements "$(cat *.egg-info/top_level.txt)"
     # https://github.com/PyCQA/pylint/issues/352
     - run: pipenv run pylint --disable=duplicate-code tests/*
     - run: pipenv run mypy "$(cat *.egg-info/top_level.txt)" tests

+ 1 - 2
.pylintrc

@@ -1,7 +1,6 @@
 [MESSAGE CONTROL]
 
-disable=bad-continuation, # black
-        missing-function-docstring,
+disable=missing-function-docstring,
         missing-module-docstring
 
 [DESIGN]

+ 2 - 0
CHANGELOG.md

@@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ## [Unreleased]
+### Removed
+- compatibility with `python3.5`
 
 ## [1.1.0] - 2021-10-06
 ### Added

+ 3 - 22
Pipfile

@@ -7,32 +7,13 @@ name = "pypi"
 switchbot-mqtt = {editable = true,path = "."}
 
 [dev-packages]
-# black requires python>=3.6
-# https://github.com/psf/black/commit/e74117f172e29e8a980e2c9de929ad50d3769150#diff-2eeaed663bd0d25b7e608891384b7298R51
-black = {version = "==20.8b1", markers = "python_version >= '3.6'"}
+black = "==20.8b1"
 mypy = "*"
-#pylint = "*"
+pylint = "*"
 pylint-import-requirements = "*"
-#pytest = "*"
+pytest = "*"
 pytest-cov = "*"
 
-# python3.5 compatibility
-# https://github.com/PyCQA/astroid/commit/d81f07becf3243a84e0baaa3493ce74cafed8022#diff-60f61ab7a8d1910d86d9fda2261620314edcae5894d5aaa236b821c7256badd7R52
-astroid = "<2.5"
-# https://github.com/pallets/click/commit/9374365fdd5af45fc3dc4047963fa30aad9ce15c
-click = "<8"
-# https://github.com/python/importlib_metadata/commit/107f9029fd5807c6579b881db19e11a0488f0675
-importlib-metadata = "<3"
-isort = "<5"
-# workaround https://github.com/pytest-dev/pytest/issues/3953
-pathlib2 = {version = "*", markers="python_version < '3.6'"}
-# https://github.com/PyCQA/pylint/commit/6e2de8e3a2e2c5586e876ca305f0844bdd822db3
-pylint = "<2.7"
-# https://github.com/pytest-dev/pytest/commit/179f4326df2b644f0ab73f78e4770dafcbdcd89f#diff-fa602a8a75dc9dcc92261bac5f533c2a85e34fcceaff63b3a3a81d9acde2fc52R52
-pytest = "<6.2"
-# https://github.com/jaraco/zipp/commit/05a3c52b4d41690e0471a2e283cffb500dc0329a
-zipp = "<2"
-
 [requires]
 python_version = "3"
 

+ 144 - 183
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "3353e3509dc8c34f46d71c669d24056582acb1b0b82237c465cc7f907b2f0afb"
+            "sha256": "a962c2c86bd4f456e14954fc1d83291b10f6b1032fb41564d8b584a85ccbbc67"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -35,10 +35,6 @@
             ],
             "version": "==0.10.1"
         },
-        "sanitized-package": {
-            "editable": true,
-            "path": "."
-        },
         "switchbot-mqtt": {
             "editable": true,
             "path": "."
@@ -54,18 +50,16 @@
         },
         "astroid": {
             "hashes": [
-                "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
-                "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
+                "sha256:304e99c129794f2cfda584a12b71fde85205da950e2f330f4be09150525ae949",
+                "sha256:9eaeaf92b3e21b70bec1a262e7eb118d2e96294892a5de595c92a12adc80dfc2"
             ],
-            "index": "pypi",
-            "version": "==2.4.2"
+            "version": "==2.8.2"
         },
         "attrs": {
             "hashes": [
                 "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
                 "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==21.2.0"
         },
         "black": {
@@ -73,82 +67,59 @@
                 "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
             ],
             "index": "pypi",
-            "markers": "python_version >= '3.6'",
             "version": "==20.8b1"
         },
         "click": {
             "hashes": [
-                "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
-                "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
+                "sha256:3fab8aeb8f15f5452ae7511ad448977b3417325bceddd53df87e0bb81f3a8cf8",
+                "sha256:7027bc7bbafaab8b2c2816861d8eb372429ee3c02e193fc2f93d6c4ab9de49c5"
             ],
-            "index": "pypi",
-            "version": "==7.1.2"
+            "version": "==8.0.2"
         },
         "coverage": {
             "hashes": [
-                "sha256:004d1880bed2d97151facef49f08e255a20ceb6f9432df75f4eef018fdd5a78c",
-                "sha256:01d84219b5cdbfc8122223b39a954820929497a1cb1422824bb86b07b74594b6",
-                "sha256:040af6c32813fa3eae5305d53f18875bedd079960822ef8ec067a66dd8afcd45",
-                "sha256:06191eb60f8d8a5bc046f3799f8a07a2d7aefb9504b0209aff0b47298333302a",
-                "sha256:13034c4409db851670bc9acd836243aeee299949bd5673e11844befcb0149f03",
-                "sha256:13c4ee887eca0f4c5a247b75398d4114c37882658300e153113dafb1d76de529",
-                "sha256:184a47bbe0aa6400ed2d41d8e9ed868b8205046518c52464fde713ea06e3a74a",
-                "sha256:18ba8bbede96a2c3dde7b868de9dcbd55670690af0988713f0603f037848418a",
-                "sha256:1aa846f56c3d49205c952d8318e76ccc2ae23303351d9270ab220004c580cfe2",
-                "sha256:217658ec7187497e3f3ebd901afdca1af062b42cfe3e0dafea4cced3983739f6",
-                "sha256:24d4a7de75446be83244eabbff746d66b9240ae020ced65d060815fac3423759",
-                "sha256:2910f4d36a6a9b4214bb7038d537f015346f413a975d57ca6b43bf23d6563b53",
-                "sha256:2949cad1c5208b8298d5686d5a85b66aae46d73eec2c3e08c817dd3513e5848a",
-                "sha256:2a3859cb82dcbda1cfd3e6f71c27081d18aa251d20a17d87d26d4cd216fb0af4",
-                "sha256:2cafbbb3af0733db200c9b5f798d18953b1a304d3f86a938367de1567f4b5bff",
-                "sha256:2e0d881ad471768bf6e6c2bf905d183543f10098e3b3640fc029509530091502",
-                "sha256:30c77c1dc9f253283e34c27935fded5015f7d1abe83bc7821680ac444eaf7793",
-                "sha256:3487286bc29a5aa4b93a072e9592f22254291ce96a9fbc5251f566b6b7343cdb",
-                "sha256:372da284cfd642d8e08ef606917846fa2ee350f64994bebfbd3afb0040436905",
-                "sha256:41179b8a845742d1eb60449bdb2992196e211341818565abded11cfa90efb821",
-                "sha256:44d654437b8ddd9eee7d1eaee28b7219bec228520ff809af170488fd2fed3e2b",
-                "sha256:4a7697d8cb0f27399b0e393c0b90f0f1e40c82023ea4d45d22bce7032a5d7b81",
-                "sha256:51cb9476a3987c8967ebab3f0fe144819781fca264f57f89760037a2ea191cb0",
-                "sha256:52596d3d0e8bdf3af43db3e9ba8dcdaac724ba7b5ca3f6358529d56f7a166f8b",
-                "sha256:53194af30d5bad77fcba80e23a1441c71abfb3e01192034f8246e0d8f99528f3",
-                "sha256:5fec2d43a2cc6965edc0bb9e83e1e4b557f76f843a77a2496cbe719583ce8184",
-                "sha256:6c90e11318f0d3c436a42409f2749ee1a115cd8b067d7f14c148f1ce5574d701",
-                "sha256:74d881fc777ebb11c63736622b60cb9e4aee5cace591ce274fb69e582a12a61a",
-                "sha256:7501140f755b725495941b43347ba8a2777407fc7f250d4f5a7d2a1050ba8e82",
-                "sha256:796c9c3c79747146ebd278dbe1e5c5c05dd6b10cc3bcb8389dfdf844f3ead638",
-                "sha256:869a64f53488f40fa5b5b9dcb9e9b2962a66a87dab37790f3fcfb5144b996ef5",
-                "sha256:8963a499849a1fc54b35b1c9f162f4108017b2e6db2c46c1bed93a72262ed083",
-                "sha256:8d0a0725ad7c1a0bcd8d1b437e191107d457e2ec1084b9f190630a4fb1af78e6",
-                "sha256:900fbf7759501bc7807fd6638c947d7a831fc9fdf742dc10f02956ff7220fa90",
-                "sha256:92b017ce34b68a7d67bd6d117e6d443a9bf63a2ecf8567bb3d8c6c7bc5014465",
-                "sha256:970284a88b99673ccb2e4e334cfb38a10aab7cd44f7457564d11898a74b62d0a",
-                "sha256:972c85d205b51e30e59525694670de6a8a89691186012535f9d7dbaa230e42c3",
-                "sha256:9a1ef3b66e38ef8618ce5fdc7bea3d9f45f3624e2a66295eea5e57966c85909e",
-                "sha256:af0e781009aaf59e25c5a678122391cb0f345ac0ec272c7961dc5455e1c40066",
-                "sha256:b6d534e4b2ab35c9f93f46229363e17f63c53ad01330df9f2d6bd1187e5eaacf",
-                "sha256:b7895207b4c843c76a25ab8c1e866261bcfe27bfaa20c192de5190121770672b",
-                "sha256:c0891a6a97b09c1f3e073a890514d5012eb256845c451bd48f7968ef939bf4ae",
-                "sha256:c2723d347ab06e7ddad1a58b2a821218239249a9e4365eaff6649d31180c1669",
-                "sha256:d1f8bf7b90ba55699b3a5e44930e93ff0189aa27186e96071fac7dd0d06a1873",
-                "sha256:d1f9ce122f83b2305592c11d64f181b87153fc2c2bbd3bb4a3dde8303cfb1a6b",
-                "sha256:d314ed732c25d29775e84a960c3c60808b682c08d86602ec2c3008e1202e3bb6",
-                "sha256:d636598c8305e1f90b439dbf4f66437de4a5e3c31fdf47ad29542478c8508bbb",
-                "sha256:deee1077aae10d8fa88cb02c845cfba9b62c55e1183f52f6ae6a2df6a2187160",
-                "sha256:ebe78fe9a0e874362175b02371bdfbee64d8edc42a044253ddf4ee7d3c15212c",
-                "sha256:f030f8873312a16414c0d8e1a1ddff2d3235655a2174e3648b4fa66b3f2f1079",
-                "sha256:f0b278ce10936db1a37e6954e15a3730bea96a0997c26d7fee88e6c396c2086d",
-                "sha256:f11642dddbb0253cc8853254301b51390ba0081750a8ac03f20ea8103f0c56b6"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
-            "version": "==5.5"
+                "sha256:07efe1fbd72e67df026ad5109bcd216acbbd4a29d5208b3dab61779bae6b7b26",
+                "sha256:0898d6948b31df13391cd40568de8f35fa5901bc922c5ae05cf070587cb9c666",
+                "sha256:0a7e55cc9f7efa22d5cc9966276ec7a40a8803676f6ccbfdc06a486fba9aa9ee",
+                "sha256:17426808e8e0824f864876312d41961223bf5e503bf8f1f846735279a60ea345",
+                "sha256:1770d24f45f1f2daeae34cfa3b33fcb29702153544cd2ad40d58399dd4ff53b5",
+                "sha256:1864bdf9b2ccb43e724051bc23a1c558daf101ad4488ede1945f2a8be1facdad",
+                "sha256:2c5f39d1556e75fc3c4fb071f9e7cfa618895a999a0de763a541d730775d0d5f",
+                "sha256:3490ff6dbf3f7accf0750136ed60ae1f487bccc1f097740e3b21262bc9c89854",
+                "sha256:353a50f123f0185cdb7a1e1e3e2cfb9d1fd7e293cfaf68eedaf5bd8e02e3ec32",
+                "sha256:3edbb3ec580c73e5a264f5d04f30245bc98eff1a26765d46c5c65134f0a0e2f7",
+                "sha256:4eb9cd910ca8e243f930243a9940ea1a522e32435d15668445753d087c30ee12",
+                "sha256:5b06f4f1729e2963281d9cd6e65e6976bf27b44d4c07ac5b47223ce45f822cec",
+                "sha256:5b1ceacb86e0a9558061dcc6baae865ed25933ea57effea644f21657cdce19bc",
+                "sha256:65da6e3e8325291f012921bbf71fea0a97824e1c573981871096aac6e2cf0ec5",
+                "sha256:66fe33e9e0df58675e08e83fe257f89e7f625e7633ea93d0872154e09cce2724",
+                "sha256:6873f3f954d3e3ab8b1881f4e5307cc19f70c9f931c41048d9f7e6fd946eabe7",
+                "sha256:73880a80fad0597eca43e213e5e1711bf6c0fcdb7eb6b01b3b17841ebe5a7f8d",
+                "sha256:7600fac458f74c68b097379f76f3a6e3a630493fc7fc94b6508fedd9d498c194",
+                "sha256:83682b73785d2e078e0b5f63410b8125b122e1a22422640c57edd4011c950f3e",
+                "sha256:83faa3692e8306b20293889714fdf573d10ef5efc5843bd7c7aea6971487bd6a",
+                "sha256:9c416ba03844608f45661a5b48dc59c6b5e89956efe388564dd138ca8caf540b",
+                "sha256:9d242a2434801ef5125330deddb4cddba8990c9a49b3dec99dca17dd7eefba5a",
+                "sha256:a2e15ab5afbee34abf716fece80ea33ea09a82e7450512f022723b1a82ec9a4e",
+                "sha256:abe8207dfb8a61ded9cd830d26c1073c8218fc0ae17eb899cfe8ec0fafae6e22",
+                "sha256:ad7182a82843f9f85487f44567c8c688f16c906bdb8d0e44ae462aed61cb8f1b",
+                "sha256:b45f89a8ef65c29195f8f28dbe215f44ccb29d934f3e862d2a5c12e38698a793",
+                "sha256:b81a4e667c45b13658b84f9b8f1d32ef86d5405fabcbd181b76b9e51d295f397",
+                "sha256:c9c413c4397d4cdc7ca89286158d240ce524f9667b52c9a64dd7e13d16cf8815",
+                "sha256:e11cca9eb5c9b3eaad899728ee2ce916138399ee8cbbccaadc1871fecb750827",
+                "sha256:e66c50f0ab445fec920a9f084914ea1776a809e3016c3738519048195f851bbb",
+                "sha256:ea452a2d83964d08232ade470091015e7ab9b8f53acbec10f2210fbab4ce7e43",
+                "sha256:f398d38e6ebc2637863db1d7be3d4f9c5174e7d24bb3b0716cdb1f204669cbcf",
+                "sha256:f82a17f2a77958f3eef40ad385fc82d4c6ba9a77a51a174efe03ce75daebbc16"
+            ],
+            "version": "==6.0.1"
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:b8de9eff2b35fb037368f28a7df1df4e6436f578fa74423505b6c6a778d5b5dd",
-                "sha256:c2d6341ff566f609e89a2acb2db190e5e1d23d5409d6cc8d2fe34d72443876d4"
+                "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15",
+                "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"
             ],
-            "index": "pypi",
-            "version": "==2.1.1"
+            "version": "==4.8.1"
         },
         "iniconfig": {
             "hashes": [
@@ -159,38 +130,37 @@
         },
         "isort": {
             "hashes": [
-                "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
-                "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
+                "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899",
+                "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"
             ],
-            "index": "pypi",
-            "version": "==4.3.21"
+            "version": "==5.9.3"
         },
         "lazy-object-proxy": {
             "hashes": [
-                "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d",
-                "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449",
-                "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08",
-                "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a",
-                "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50",
-                "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd",
-                "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239",
-                "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb",
-                "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea",
-                "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e",
-                "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156",
-                "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142",
-                "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442",
-                "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62",
-                "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db",
-                "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531",
-                "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383",
-                "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a",
-                "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357",
-                "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4",
-                "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.4.3"
+                "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653",
+                "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61",
+                "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2",
+                "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837",
+                "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3",
+                "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43",
+                "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726",
+                "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3",
+                "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587",
+                "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8",
+                "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a",
+                "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd",
+                "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f",
+                "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad",
+                "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4",
+                "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b",
+                "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf",
+                "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981",
+                "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741",
+                "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e",
+                "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
+                "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
+            ],
+            "version": "==1.6.0"
         },
         "mccabe": {
             "hashes": [
@@ -240,18 +210,8 @@
                 "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
                 "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
             ],
-            "markers": "python_version >= '3.6'",
             "version": "==21.0"
         },
-        "pathlib2": {
-            "hashes": [
-                "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8",
-                "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"
-            ],
-            "index": "pypi",
-            "markers": "python_version < '3.6'",
-            "version": "==2.3.6"
-        },
         "pathspec": {
             "hashes": [
                 "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
@@ -259,29 +219,34 @@
             ],
             "version": "==0.9.0"
         },
+        "platformdirs": {
+            "hashes": [
+                "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
+                "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
+            ],
+            "version": "==2.4.0"
+        },
         "pluggy": {
             "hashes": [
-                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
-                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
+                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
+                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==0.13.1"
+            "version": "==1.0.0"
         },
         "py": {
             "hashes": [
                 "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
                 "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
             ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.10.0"
         },
         "pylint": {
             "hashes": [
-                "sha256:718b74786ea7ed07aa0c58bf572154d4679f960d26e9641cc1de204a30b87fc9",
-                "sha256:e71c2e9614a4f06e36498f310027942b0f4f2fde20aebb01655b31edc63b9eaf"
+                "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
+                "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
             ],
             "index": "pypi",
-            "version": "==2.6.2"
+            "version": "==2.11.1"
         },
         "pylint-import-requirements": {
             "hashes": [
@@ -296,87 +261,84 @@
                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.4.7"
         },
         "pytest": {
             "hashes": [
-                "sha256:4288fed0d9153d9646bfcdf0c0428197dba1ecb27a33bb6e031d002fa88653fe",
-                "sha256:c0a7e94a8cdbc5422a51ccdad8e6f1024795939cc89159a0ae7f0b316ad3823e"
+                "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89",
+                "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"
             ],
             "index": "pypi",
-            "version": "==6.1.2"
+            "version": "==6.2.5"
         },
         "pytest-cov": {
             "hashes": [
-                "sha256:261bb9e47e65bd099c89c3edf92972865210c36813f80ede5277dceb77a4a62a",
-                "sha256:261ceeb8c227b726249b376b8526b600f38667ee314f910353fa318caa01f4d7"
+                "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6",
+                "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"
             ],
             "index": "pypi",
-            "version": "==2.12.1"
+            "version": "==3.0.0"
         },
         "regex": {
             "hashes": [
-                "sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
-                "sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
-                "sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
-                "sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
-                "sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
-                "sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
-                "sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
-                "sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
-                "sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
-                "sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
-                "sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
-                "sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
-                "sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
-                "sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
-                "sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
-                "sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
-                "sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
-                "sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
-                "sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
-                "sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
-                "sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
-                "sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
-                "sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
-                "sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
-                "sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
-                "sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
-                "sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
-                "sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
-                "sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
-                "sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
-                "sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
-                "sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
-                "sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
-                "sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
-                "sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
-                "sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
-                "sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
-                "sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
-                "sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
-                "sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
-                "sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
-            ],
-            "version": "==2021.8.28"
-        },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
-            "version": "==1.16.0"
+                "sha256:09e1031e2059abd91177c302da392a7b6859ceda038be9e015b522a182c89e4f",
+                "sha256:176796cb7f82a7098b0c436d6daac82f57b9101bb17b8e8119c36eecf06a60a3",
+                "sha256:1abbd95cbe9e2467cac65c77b6abd9223df717c7ae91a628502de67c73bf6838",
+                "sha256:1ce02f420a7ec3b2480fe6746d756530f69769292eca363218c2291d0b116a01",
+                "sha256:1f51926db492440e66c89cd2be042f2396cf91e5b05383acd7372b8cb7da373f",
+                "sha256:26895d7c9bbda5c52b3635ce5991caa90fbb1ddfac9c9ff1c7ce505e2282fb2a",
+                "sha256:2efd47704bbb016136fe34dfb74c805b1ef5c7313aef3ce6dcb5ff844299f432",
+                "sha256:36c98b013273e9da5790ff6002ab326e3f81072b4616fd95f06c8fa733d2745f",
+                "sha256:39079ebf54156be6e6902f5c70c078f453350616cfe7bfd2dd15bdb3eac20ccc",
+                "sha256:3d52c5e089edbdb6083391faffbe70329b804652a53c2fdca3533e99ab0580d9",
+                "sha256:45cb0f7ff782ef51bc79e227a87e4e8f24bc68192f8de4f18aae60b1d60bc152",
+                "sha256:4786dae85c1f0624ac77cb3813ed99267c9adb72e59fdc7297e1cf4d6036d493",
+                "sha256:51feefd58ac38eb91a21921b047da8644155e5678e9066af7bcb30ee0dca7361",
+                "sha256:55ef044899706c10bc0aa052f2fc2e58551e2510694d6aae13f37c50f3f6ff61",
+                "sha256:5e5796d2f36d3c48875514c5cd9e4325a1ca172fc6c78b469faa8ddd3d770593",
+                "sha256:5f199419a81c1016e0560c39773c12f0bd924c37715bffc64b97140d2c314354",
+                "sha256:5f55c4804797ef7381518e683249310f7f9646da271b71cb6b3552416c7894ee",
+                "sha256:74e55f8d66f1b41d44bc44c891bcf2c7fad252f8f323ee86fba99d71fd1ad5e3",
+                "sha256:7f125fce0a0ae4fd5c3388d369d7a7d78f185f904c90dd235f7ecf8fe13fa741",
+                "sha256:82cfb97a36b1a53de32b642482c6c46b6ce80803854445e19bc49993655ebf3b",
+                "sha256:88dc3c1acd3f0ecfde5f95c32fcb9beda709dbdf5012acdcf66acbc4794468eb",
+                "sha256:924079d5590979c0e961681507eb1773a142553564ccae18d36f1de7324e71ca",
+                "sha256:973499dac63625a5ef9dfa4c791aa33a502ddb7615d992bdc89cf2cc2285daa3",
+                "sha256:981c786293a3115bc14c103086ae54e5ee50ca57f4c02ce7cf1b60318d1e8072",
+                "sha256:9c070d5895ac6aeb665bd3cd79f673775caf8d33a0b569e98ac434617ecea57d",
+                "sha256:9e3e2cea8f1993f476a6833ef157f5d9e8c75a59a8d8b0395a9a6887a097243b",
+                "sha256:9e527ab1c4c7cf2643d93406c04e1d289a9d12966529381ce8163c4d2abe4faf",
+                "sha256:a37305eb3199d8f0d8125ec2fb143ba94ff6d6d92554c4b8d4a8435795a6eccd",
+                "sha256:aa0ab3530a279a3b7f50f852f1bab41bc304f098350b03e30a3876b7dd89840e",
+                "sha256:b04e512eb628ea82ed86eb31c0f7fc6842b46bf2601b66b1356a7008327f7700",
+                "sha256:b09d3904bf312d11308d9a2867427479d277365b1617e48ad09696fa7dfcdf59",
+                "sha256:b8b6ee6555b6fbae578f1468b3f685cdfe7940a65675611365a7ea1f8d724991",
+                "sha256:b9b5c215f3870aa9b011c00daeb7be7e1ae4ecd628e9beb6d7e6107e07d81287",
+                "sha256:c6569ba7b948c3d61d27f04e2b08ebee24fec9ff8e9ea154d8d1e975b175bfa7",
+                "sha256:e4204708fa116dd03436a337e8e84261bc8051d058221ec63535c9403a1582a1",
+                "sha256:ea8de658d7db5987b11097445f2b1f134400e2232cb40e614e5f7b6f5428710e",
+                "sha256:f540f153c4f5617bc4ba6433534f8916d96366a08797cbbe4132c37b70403e92",
+                "sha256:fab3ab8aedfb443abb36729410403f0fe7f60ad860c19a979d47fb3eb98ef820",
+                "sha256:fb2baff66b7d2267e07ef71e17d01283b55b3cc51a81b54cc385e721ae172ba4",
+                "sha256:fe6ce4f3d3c48f9f402da1ceb571548133d3322003ce01b20d960a82251695d2",
+                "sha256:ff24897f6b2001c38a805d53b6ae72267025878d35ea225aa24675fbff2dba7f"
+            ],
+            "version": "==2021.10.8"
         },
         "toml": {
             "hashes": [
                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                 "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
-            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.10.2"
         },
+        "tomli": {
+            "hashes": [
+                "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f",
+                "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"
+            ],
+            "version": "==1.2.1"
+        },
         "typed-ast": {
             "hashes": [
                 "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
@@ -428,11 +390,10 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
-                "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
+                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
+                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
             ],
-            "index": "pypi",
-            "version": "==1.2.0"
+            "version": "==3.6.0"
         }
     }
 }

+ 5 - 2
setup.py

@@ -33,7 +33,9 @@ setuptools.setup(
     description="MQTT client controlling SwitchBot button & curtain automators, "
     # https://www.home-assistant.io/integrations/switch.mqtt/
     "compatible with home-assistant.io's MQTT Switch & Cover platform",
-    long_description=pathlib.Path(__file__).parent.joinpath("README.md").read_text(),
+    long_description=pathlib.Path(__file__)
+    .parent.joinpath("README.md")
+    .read_text(encoding="utf8"),
     long_description_content_type="text/markdown",
     author="Fabian Peter Hammerle",
     author_email="fabian@hammerle.me",
@@ -63,7 +65,6 @@ setuptools.setup(
         "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
         "Operating System :: POSIX :: Linux",
         # .github/workflows/python.yml
-        "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
@@ -71,6 +72,8 @@ setuptools.setup(
         "Topic :: Home Automation",
     ],
     entry_points={"console_scripts": ["switchbot-mqtt = switchbot_mqtt:_main"]},
+    # variable type hints, f-strings & * to force keyword-only arguments
+    python_requires=">=3.6",
     install_requires=[
         # >=1.3.0 for btle.BTLEManagementError (could be replaced with BTLEException)
         # >=0.1.0 for btle.helperExe

+ 28 - 30
switchbot_mqtt/__init__.py

@@ -43,7 +43,7 @@ class _MQTTTopicPlaceholder(enum.Enum):
 
 _MQTTTopicLevel = typing.Union[str, _MQTTTopicPlaceholder]
 # "homeassistant" for historic reason, may be parametrized in future
-_MQTT_TOPIC_LEVELS_PREFIX = ["homeassistant"]  # type: typing.List[_MQTTTopicLevel]
+_MQTT_TOPIC_LEVELS_PREFIX: typing.List[_MQTTTopicLevel] = ["homeassistant"]
 
 
 def _join_mqtt_topic_levels(
@@ -77,6 +77,7 @@ class _MQTTCallbackUserdata:
     # pylint: disable=too-few-public-methods; @dataclasses.dataclass when python_requires>=3.7
     def __init__(
         self,
+        *,
         retry_count: int,
         device_passwords: typing.Dict[str, str],
         fetch_device_info: bool,
@@ -90,12 +91,12 @@ class _MQTTCallbackUserdata:
 
 
 class _MQTTControlledActor(abc.ABC):
-    MQTT_COMMAND_TOPIC_LEVELS = NotImplemented  # type: typing.List[_MQTTTopicLevel]
-    MQTT_STATE_TOPIC_LEVELS = NotImplemented  # type: typing.List[_MQTTTopicLevel]
+    MQTT_COMMAND_TOPIC_LEVELS: typing.List[_MQTTTopicLevel] = NotImplemented
+    MQTT_STATE_TOPIC_LEVELS: typing.List[_MQTTTopicLevel] = NotImplemented
 
     @abc.abstractmethod
     def __init__(
-        self, mac_address: str, retry_count: int, password: typing.Optional[str]
+        self, *, mac_address: str, retry_count: int, password: typing.Optional[str]
     ) -> None:
         # alternative: pySwitchbot >=0.10.0 provides SwitchbotDevice.get_mac()
         self._mac_address = mac_address
@@ -166,6 +167,7 @@ class _MQTTControlledActor(abc.ABC):
 
     def _mqtt_publish(
         self,
+        *,
         topic_levels: typing.List[_MQTTTopicLevel],
         payload: bytes,
         mqtt_client: paho.mqtt.client.Client,
@@ -175,9 +177,9 @@ class _MQTTControlledActor(abc.ABC):
         )
         # https://pypi.org/project/paho-mqtt/#publishing
         _LOGGER.debug("publishing topic=%s payload=%r", topic, payload)
-        message_info = mqtt_client.publish(
+        message_info: paho.mqtt.client.MQTTMessageInfo = mqtt_client.publish(
             topic=topic, payload=payload, retain=True
-        )  # type: paho.mqtt.client.MQTTMessageInfo
+        )
         # wait before checking status?
         if message_info.rc != paho.mqtt.client.MQTT_ERR_SUCCESS:
             _LOGGER.error(
@@ -212,7 +214,7 @@ class _ButtonAutomator(_MQTTControlledActor):
     ]
 
     def __init__(
-        self, mac_address: str, retry_count: int, password: typing.Optional[str]
+        self, *, mac_address: str, retry_count: int, password: typing.Optional[str]
     ) -> None:
         self._device = switchbot.Switchbot(
             mac=mac_address, password=password, retry_count=retry_count
@@ -279,7 +281,7 @@ class _CurtainMotor(_MQTTControlledActor):
         )
 
     def __init__(
-        self, mac_address: str, retry_count: int, password: typing.Optional[str]
+        self, *, mac_address: str, retry_count: int, password: typing.Optional[str]
     ) -> None:
         # > The position of the curtain is saved in self._pos with 0 = open and 100 = closed.
         # https://github.com/Danielhiversen/pySwitchbot/blob/0.10.0/switchbot/__init__.py#L150
@@ -308,7 +310,7 @@ class _CurtainMotor(_MQTTControlledActor):
         )
 
     def _update_position(self, mqtt_client: paho.mqtt.client.Client) -> None:
-        log_queue = queue.Queue(maxsize=0)  # type: queue.Queue[logging.LogRecord]
+        log_queue: queue.Queue[logging.LogRecord] = queue.Queue(maxsize=0)
         logging.getLogger("switchbot").addHandler(_QueueLogHandler(log_queue))
         try:
             self._device.update()
@@ -317,7 +319,7 @@ class _CurtainMotor(_MQTTControlledActor):
             while not log_queue.empty():
                 log_record = log_queue.get()
                 if log_record.exc_info:
-                    exc = log_record.exc_info[1]  # type: typing.Optional[BaseException]
+                    exc: typing.Optional[BaseException] = log_record.exc_info[1]
                     if (
                         isinstance(exc, bluepy.btle.BTLEManagementError)
                         and exc.emsg == "Permission Denied"
@@ -330,23 +332,18 @@ class _CurtainMotor(_MQTTControlledActor):
             ):
                 raise PermissionError(
                     "bluepy-helper failed to enable low energy mode"
-                    + " due to insufficient permissions."
-                    + "\nSee {}, {}, and {}.".format(
-                        "https://github.com/IanHarvey/bluepy/issues/313#issuecomment-428324639",
-                        "https://github.com/fphammerle/switchbot-mqtt/pull/31"
-                        + "#issuecomment-846383603",
-                        "https://github.com/IanHarvey/bluepy/blob/v/1.3.0/bluepy/bluepy-helper.c"
-                        + "#L1260",
-                    )
-                    + "\nInsecure workaround:"
-                    + "\n1. sudo apt-get install --no-install-recommends libcap2-bin"
-                    + "\n2. sudo setcap cap_net_admin+ep {}".format(
-                        shlex.quote(bluepy.btle.helperExe)
-                    )
-                    + "\n3. restart switchbot-mqtt"
-                    + "\nIn docker-based setups, you could use"
-                    + " `sudo docker run --cap-drop ALL --cap-add NET_ADMIN --user 0 …`"
-                    + " (seriously insecure)."
+                    " due to insufficient permissions."
+                    "\nSee https://github.com/IanHarvey/bluepy/issues/313#issuecomment-428324639"
+                    ", https://github.com/fphammerle/switchbot-mqtt/pull/31#issuecomment-846383603"
+                    ", and https://github.com/IanHarvey/bluepy/blob/v/1.3.0/bluepy"
+                    "/bluepy-helper.c#L1260."
+                    "\nInsecure workaround:"
+                    "\n1. sudo apt-get install --no-install-recommends libcap2-bin"
+                    f"\n2. sudo setcap cap_net_admin+ep {shlex.quote(bluepy.btle.helperExe)}"
+                    "\n3. restart switchbot-mqtt"
+                    "\nIn docker-based setups, you could use"
+                    " `sudo docker run --cap-drop ALL --cap-add NET_ADMIN --user 0 …`"
+                    " (seriously insecure)."
                 ) from exc
             raise
         self._report_position(mqtt_client=mqtt_client)
@@ -407,6 +404,7 @@ def _mqtt_on_connect(
 
 
 def _run(
+    *,
     mqtt_host: str,
     mqtt_port: int,
     mqtt_username: typing.Optional[str],
@@ -475,9 +473,9 @@ def _main() -> None:
     argparser.add_argument(
         "--fetch-device-info",  # generic name to cover future addition of battery level etc.
         action="store_true",
-        help="Report curtain motors' position on topic {} after sending stop command.".format(
-            _CurtainMotor.get_mqtt_position_topic(mac_address="MAC_ADDRESS")
-        ),
+        help="Report curtain motors' position on"
+        f" topic {_CurtainMotor.get_mqtt_position_topic(mac_address='MAC_ADDRESS')}"
+        " after sending stop command.",
     )
     args = argparser.parse_args()
     if args.mqtt_password_path:

+ 11 - 13
tests/test_mqtt.py

@@ -83,12 +83,12 @@ def test__run(
         (
             "switchbot_mqtt",
             logging.INFO,
-            "connecting to MQTT broker {}:{}".format(mqtt_host, mqtt_port),
+            f"connecting to MQTT broker {mqtt_host}:{mqtt_port}",
         ),
         (
             "switchbot_mqtt",
             logging.DEBUG,
-            "connected to MQTT broker {}:{}".format(mqtt_host, mqtt_port),
+            f"connected to MQTT broker {mqtt_host}:{mqtt_port}",
         ),
         (
             "switchbot_mqtt",
@@ -253,7 +253,7 @@ def test__mqtt_command_callback(
         (
             "switchbot_mqtt",
             logging.DEBUG,
-            "received topic={} payload={!r}".format(topic.decode(), payload),
+            f"received topic={topic.decode()} payload={payload!r}",
         )
     ]
 
@@ -334,12 +334,12 @@ def test__mqtt_command_callback_unexpected_topic(caplog, topic: bytes, payload:
         (
             "switchbot_mqtt",
             logging.DEBUG,
-            "received topic={} payload={!r}".format(topic.decode(), payload),
+            f"received topic={topic.decode()} payload={payload!r}",
         ),
         (
             "switchbot_mqtt",
             logging.WARNING,
-            "unexpected topic {}".format(topic.decode()),
+            f"unexpected topic {topic.decode()}",
         ),
     ]
 
@@ -351,7 +351,7 @@ def test__mqtt_command_callback_invalid_mac_address(
     ActorMock = _mock_actor_class(
         switchbot_mqtt._ButtonAutomator.MQTT_COMMAND_TOPIC_LEVELS
     )
-    topic = "homeassistant/switch/switchbot/{}/set".format(mac_address).encode()
+    topic = f"homeassistant/switch/switchbot/{mac_address}/set".encode()
     message = MQTTMessage(topic=topic)
     message.payload = payload
     with unittest.mock.patch.object(
@@ -374,12 +374,12 @@ def test__mqtt_command_callback_invalid_mac_address(
         (
             "switchbot_mqtt",
             logging.DEBUG,
-            "received topic={} payload={!r}".format(topic.decode(), payload),
+            f"received topic={topic.decode()} payload={payload!r}",
         ),
         (
             "switchbot_mqtt",
             logging.WARNING,
-            "invalid mac address {}".format(mac_address),
+            f"invalid mac address {mac_address}",
         ),
     ]
 
@@ -415,7 +415,7 @@ def test__mqtt_command_callback_ignore_retained(caplog, topic: bytes, payload: b
         (
             "switchbot_mqtt",
             logging.DEBUG,
-            "received topic={} payload={!r}".format(topic.decode(), payload),
+            f"received topic={topic.decode()} payload={payload!r}",
         ),
         ("switchbot_mqtt", logging.INFO, "ignoring retained message"),
     ]
@@ -475,7 +475,7 @@ def test__report_state(
     assert caplog.record_tuples[0] == (
         "switchbot_mqtt",
         logging.DEBUG,
-        "publishing topic={} payload={!r}".format(expected_topic, state),
+        f"publishing topic={expected_topic} payload={state!r}",
     )
     if return_code == MQTT_ERR_SUCCESS:
         assert not caplog.records[1:]
@@ -484,8 +484,6 @@ def test__report_state(
             (
                 "switchbot_mqtt",
                 logging.ERROR,
-                "Failed to publish MQTT message on topic {} (rc={})".format(
-                    expected_topic, return_code
-                ),
+                f"Failed to publish MQTT message on topic {expected_topic} (rc={return_code})",
             )
         ]

+ 5 - 11
tests/test_switchbot_button_automator.py

@@ -77,9 +77,7 @@ def test_execute_command(
             (
                 "switchbot_mqtt",
                 logging.INFO,
-                "switchbot {} turned {}".format(
-                    mac_address, message_payload.decode().lower()
-                ),
+                f"switchbot {mac_address} turned {message_payload.decode().lower()}",
             )
         ]
         report_mock.assert_called_once_with(
@@ -90,9 +88,7 @@ def test_execute_command(
             (
                 "switchbot_mqtt",
                 logging.ERROR,
-                "failed to turn {} switchbot {}".format(
-                    message_payload.decode().lower(), mac_address
-                ),
+                f"failed to turn {message_payload.decode().lower()} switchbot {mac_address}",
             )
         ]
         report_mock.assert_not_called()
@@ -120,7 +116,7 @@ def test_execute_command_invalid_payload(caplog, mac_address, message_payload):
         (
             "switchbot_mqtt",
             logging.WARNING,
-            "unexpected payload {!r} (expected 'ON' or 'OFF')".format(message_payload),
+            f"unexpected payload {message_payload!r} (expected 'ON' or 'OFF')",
         )
     ]
 
@@ -137,7 +133,7 @@ def test_execute_command_bluetooth_error(caplog, mac_address, message_payload):
     with unittest.mock.patch(
         "bluepy.btle.Peripheral",
         side_effect=bluepy.btle.BTLEDisconnectError(
-            "Failed to connect to peripheral {}, addr type: random".format(mac_address)
+            f"Failed to connect to peripheral {mac_address}, addr type: random"
         ),
     ), caplog.at_level(logging.ERROR):
         switchbot_mqtt._ButtonAutomator(
@@ -156,8 +152,6 @@ def test_execute_command_bluetooth_error(caplog, mac_address, message_payload):
         (
             "switchbot_mqtt",
             logging.ERROR,
-            "failed to turn {} switchbot {}".format(
-                message_payload.decode().lower(), mac_address
-            ),
+            f"failed to turn {message_payload.decode().lower()} switchbot {mac_address}",
         ),
     ]

+ 12 - 19
tests/test_switchbot_curtain_motor.py

@@ -30,9 +30,10 @@ import switchbot_mqtt
 
 @pytest.mark.parametrize("mac_address", ["{MAC_ADDRESS}", "aa:bb:cc:dd:ee:ff"])
 def test_get_mqtt_position_topic(mac_address):
-    assert switchbot_mqtt._CurtainMotor.get_mqtt_position_topic(
-        mac_address=mac_address
-    ) == "homeassistant/cover/switchbot-curtain/{}/position".format(mac_address)
+    assert (
+        switchbot_mqtt._CurtainMotor.get_mqtt_position_topic(mac_address=mac_address)
+        == f"homeassistant/cover/switchbot-curtain/{mac_address}/position"
+    )
 
 
 @pytest.mark.parametrize(
@@ -168,16 +169,14 @@ def test_execute_command(
     )
     action_mock.assert_called_once_with()
     if command_successful:
+        state_str = {b"open": "opening", b"close": "closing", b"stop": "stopped"}[
+            message_payload.lower()
+        ]
         assert caplog.record_tuples == [
             (
                 "switchbot_mqtt",
                 logging.INFO,
-                "switchbot curtain {} {}".format(
-                    mac_address,
-                    {b"open": "opening", b"close": "closing", b"stop": "stopped"}[
-                        message_payload.lower()
-                    ],
-                ),
+                f"switchbot curtain {mac_address} {state_str}",
             )
         ]
         report_mock.assert_called_once_with(
@@ -192,9 +191,7 @@ def test_execute_command(
             (
                 "switchbot_mqtt",
                 logging.ERROR,
-                "failed to {} switchbot curtain {}".format(
-                    message_payload.decode().lower(), mac_address
-                ),
+                f"failed to {message_payload.decode().lower()} switchbot curtain {mac_address}",
             )
         ]
         report_mock.assert_not_called()
@@ -235,9 +232,7 @@ def test_execute_command_invalid_payload(
         (
             "switchbot_mqtt",
             logging.WARNING,
-            "unexpected payload {!r} (expected 'OPEN', 'CLOSE', or 'STOP')".format(
-                message_payload
-            ),
+            f"unexpected payload {message_payload!r} (expected 'OPEN', 'CLOSE', or 'STOP')",
         )
     ]
 
@@ -254,7 +249,7 @@ def test_execute_command_bluetooth_error(caplog, mac_address, message_payload):
     with unittest.mock.patch(
         "bluepy.btle.Peripheral",
         side_effect=bluepy.btle.BTLEDisconnectError(
-            "Failed to connect to peripheral {}, addr type: random".format(mac_address)
+            f"Failed to connect to peripheral {mac_address}, addr type: random"
         ),
     ), caplog.at_level(logging.ERROR):
         switchbot_mqtt._CurtainMotor(
@@ -273,8 +268,6 @@ def test_execute_command_bluetooth_error(caplog, mac_address, message_payload):
         (
             "switchbot_mqtt",
             logging.ERROR,
-            "failed to {} switchbot curtain {}".format(
-                message_payload.decode().lower(), mac_address
-            ),
+            f"failed to {message_payload.decode().lower()} switchbot curtain {mac_address}",
         ),
     ]