Browse Source

drop compatibility with python3.5; test against python3.10

checked for:
- `bad-continuation` in `.pylintrc`
- wording in `.github/dependabot.yml`
- use of `MagicMock.call_count` instead of `.assert_called_once()` and `.assert_not_called()`
- classifiers and `python_requires` in `setup.py`
- `str.format()`
- legacy `# type:` hints
- `ImportError` instead of `ModuleNotFoundError`
- diff of `.github/workflows/python.yml` with `ical2vdir`'s version
- enforcement of keyword-only arguments
- remark in changelog

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
https://github.com/fphammerle/switchbot-mqtt/commit/fcb34cee5e2edf199d6971555c8a5525b43ae2d6
Fabian Peter Hammerle 1 week ago
parent
commit
4e1d3ec4a5

+ 7 - 1
.github/dependabot.yml

@@ -7,10 +7,16 @@ 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
     day: friday
+- package-ecosystem: github-actions
+  directory: /
+  # > YAML aliases are not supported
+  schedule:
+    interval: weekly
+    day: friday
 
 # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates

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

@@ -34,26 +34,25 @@ jobs:
     strategy:
       matrix:
         python-version:
-        - 3.5
-        - 3.6
-        - 3.7
-        - 3.8
-        - 3.9
+        - '3.6'
+        - '3.7'
+        - '3.8'
+        - '3.9'
+        - '3.10'
       fail-fast: false
     steps:
-    - uses: actions/checkout@v1
-    - uses: actions/setup-python@v1
+    - uses: actions/checkout@v2.3.4
+    - uses: actions/setup-python@v2.2.2
       with:
         python-version: ${{ matrix.python-version }}
     - run: pip install --upgrade pipenv==2020.8.13
+    # by default pipenv picks the latest version in PATH
     - run: pipenv install --python "$PYTHON_VERSION" --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=94
-    # 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 tests/*
     - run: pipenv run mypy "$(cat *.egg-info/top_level.txt)" tests

+ 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`
 
 ## [2.7.3] - 2021-04-20
 ### Fixed

+ 8 - 25
Pipfile

@@ -15,37 +15,20 @@ cc1101 = {editable = true,path = "."}
 spidev = "==3.4"
 
 [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"
 # >=0.1.1 for https://github.com/blu3r4y/blinkcheck/pull/2
 blinkcheck = {version = ">=0.1.1", markers = "python_version >= '3.8'"}
 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"
-# blinkcheck
-packaging = {version = "*", markers = "python_version >= '3.8'"}
-# 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"
-# blinkcheck
-requests = {version = "*", markers = "python_version >= '3.8'"}
-# https://github.com/jaraco/zipp/commit/05a3c52b4d41690e0471a2e283cffb500dc0329a
-zipp = "<2"
+# python3.10 compatibility
+# >   File "[...]/lib/python3.10/site-packages/mypy/main.py", line 11, in <module>
+# >     from typing_extensions import Final, NoReturn
+# > ModuleNotFoundError: No module named 'typing_extensions'
+typing_extensions = {version = "*", markers = ""}
 
 [requires]
 python_version = "3"

+ 158 - 187
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "752acbdf4d148244fe61b96e85ecdd2ee95308c290a143d2fbf38a9c9b12d39b"
+            "sha256": "8472cba3a525f07d424827b1f064956f3796f65fbba7b1fbc63e7f6621a2381f"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -38,11 +38,10 @@
         },
         "astroid": {
             "hashes": [
-                "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703",
-                "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"
+                "sha256:304e99c129794f2cfda584a12b71fde85205da950e2f330f4be09150525ae949",
+                "sha256:9eaeaf92b3e21b70bec1a262e7eb118d2e96294892a5de595c92a12adc80dfc2"
             ],
-            "index": "pypi",
-            "version": "==2.4.2"
+            "version": "==2.8.2"
         },
         "attrs": {
             "hashes": [
@@ -56,7 +55,6 @@
                 "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"
             ],
             "index": "pypi",
-            "markers": "python_version >= '3.6'",
             "version": "==20.8b1"
         },
         "blinkcheck": {
@@ -70,83 +68,63 @@
         },
         "certifi": {
             "hashes": [
-                "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee",
-                "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8"
+                "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872",
+                "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"
             ],
-            "version": "==2021.5.30"
+            "version": "==2021.10.8"
         },
         "charset-normalizer": {
             "hashes": [
-                "sha256:0c8911edd15d19223366a194a513099a302055a962bca2cec0f54b8b63175d8b",
-                "sha256:f23667ebe1084be45f6ae0538e4a5a865206544097e4e8bbcacf42cd02a348f3"
+                "sha256:5d209c0a931f215cee683b6445e2d77677e7e75e159f78def0db09d68fafcaa6",
+                "sha256:5ec46d183433dcbd0ab716f2d7f29d8dee50505b3fdb40c6b985c7c4f5a3591f"
             ],
             "markers": "python_version >= '3'",
-            "version": "==2.0.4"
+            "version": "==2.0.6"
         },
         "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"
-            ],
-            "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"
         },
         "idna": {
             "hashes": [
@@ -158,11 +136,10 @@
         },
         "importlib-metadata": {
             "hashes": [
-                "sha256:b8de9eff2b35fb037368f28a7df1df4e6436f578fa74423505b6c6a778d5b5dd",
-                "sha256:c2d6341ff566f609e89a2acb2db190e5e1d23d5409d6cc8d2fe34d72443876d4"
+                "sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15",
+                "sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1"
             ],
-            "index": "pypi",
-            "version": "==2.1.1"
+            "version": "==4.8.1"
         },
         "iniconfig": {
             "hashes": [
@@ -173,37 +150,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"
-            ],
-            "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": [
@@ -253,19 +230,8 @@
                 "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7",
                 "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"
             ],
-            "index": "pypi",
-            "markers": "python_version >= '3.8'",
             "version": "==21.0"
         },
-        "pathlib2": {
-            "hashes": [
-                "sha256:3a130b266b3a36134dcc79c17b3c7ac9634f083825ca6ea9d8f557ee6195c9c8",
-                "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"
-            ],
-            "index": "pypi",
-            "markers": "python_version < '3.6'",
-            "version": "==2.3.6"
-        },
         "pathspec": {
             "hashes": [
                 "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a",
@@ -273,12 +239,19 @@
             ],
             "version": "==0.9.0"
         },
+        "platformdirs": {
+            "hashes": [
+                "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
+                "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
+            ],
+            "version": "==2.4.0"
+        },
         "pluggy": {
             "hashes": [
-                "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
-                "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
+                "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159",
+                "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"
             ],
-            "version": "==0.13.1"
+            "version": "==1.0.0"
         },
         "py": {
             "hashes": [
@@ -289,11 +262,11 @@
         },
         "pylint": {
             "hashes": [
-                "sha256:718b74786ea7ed07aa0c58bf572154d4679f960d26e9641cc1de204a30b87fc9",
-                "sha256:e71c2e9614a4f06e36498f310027942b0f4f2fde20aebb01655b31edc63b9eaf"
+                "sha256:0f358e221c45cbd4dad2a1e4b883e75d28acdcccd29d40c76eb72b307269b126",
+                "sha256:2c9843fff1a88ca0ad98a256806c82c5a8f86086e7ccbdb93297d86c3f90c436"
             ],
             "index": "pypi",
-            "version": "==2.6.2"
+            "version": "==2.11.1"
         },
         "pylint-import-requirements": {
             "hashes": [
@@ -312,82 +285,73 @@
         },
         "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"
+                "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"
         },
         "requests": {
             "hashes": [
                 "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24",
                 "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"
             ],
-            "index": "pypi",
-            "markers": "python_version >= '3.8'",
             "version": "==2.26.0"
         },
-        "six": {
-            "hashes": [
-                "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
-                "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
-            ],
-            "version": "==1.16.0"
-        },
         "toml": {
             "hashes": [
                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
@@ -395,6 +359,13 @@
             ],
             "version": "==0.10.2"
         },
+        "tomli": {
+            "hashes": [
+                "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f",
+                "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442"
+            ],
+            "version": "==1.2.1"
+        },
         "typed-ast": {
             "hashes": [
                 "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace",
@@ -432,18 +403,19 @@
         },
         "typing-extensions": {
             "hashes": [
-                "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497",
-                "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342",
-                "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84"
+                "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e",
+                "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7",
+                "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"
             ],
-            "version": "==3.10.0.0"
+            "index": "pypi",
+            "version": "==3.10.0.2"
         },
         "urllib3": {
             "hashes": [
-                "sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
-                "sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
+                "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
+                "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
             ],
-            "version": "==1.26.6"
+            "version": "==1.26.7"
         },
         "wrapt": {
             "hashes": [
@@ -453,11 +425,10 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
-                "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
+                "sha256:71c644c5369f4a6e07636f0aa966270449561fcea2e3d6747b8d23efaa9d7832",
+                "sha256:9fe5ea21568a0a70e50f273397638d39b03353731e6cbbb3fd8502a33fec40bc"
             ],
-            "index": "pypi",
-            "version": "==1.2.0"
+            "version": "==3.6.0"
         }
     }
 }

+ 52 - 69
cc1101/__init__.py

@@ -28,11 +28,11 @@ import spidev
 
 import cc1101._gpio
 from cc1101.addresses import (
-    StrobeAddress,
     ConfigurationRegisterAddress,
-    StatusRegisterAddress,
-    PatableAddress,
     FIFORegisterAddress,
+    PatableAddress,
+    StatusRegisterAddress,
+    StrobeAddress,
 )
 from cc1101.options import (
     GDOSignalSelection,
@@ -42,7 +42,6 @@ from cc1101.options import (
     _TransceiveMode,
 )
 
-
 _LOGGER = logging.getLogger(__name__)
 
 
@@ -74,7 +73,7 @@ class _ReceivedPacket:  # unstable
 
     def __init__(
         self,
-        # *,
+        *,
         payload: bytes,
         rssi_index: int,  # byte
         checksum_valid: bool,
@@ -99,19 +98,17 @@ class _ReceivedPacket:  # unstable
         return self._rssi_index / 2 - self._RSSI_OFFSET_dB
 
     def __str__(self) -> str:
-        return "{}(RSSI {:.0f}dBm, 0x{})".format(
-            type(self).__name__, self.rssi_dbm, self.payload.hex()
-        )
+        return f"{type(self).__name__}(RSSI {self.rssi_dbm:.0f}dBm, 0x{self.payload.hex()})"
 
 
 def _format_patable(settings: typing.Iterable[int], insert_spaces: bool) -> str:
     # "Table 39: Optimum PATABLE Settings" uses hexadecimal digits
     # "0" for brevity
-    settings_hex = tuple(map(lambda s: "0" if s == 0 else "0x{:x}".format(s), settings))
+    settings_hex = tuple(map(lambda s: "0" if s == 0 else f"0x{s:x}", settings))
     if len(settings_hex) == 1:
-        return "({},)".format(settings_hex[0])
+        return f"({settings_hex[0]},)"
     delimiter = ", " if insert_spaces else ","
-    return "({})".format(delimiter.join(settings_hex))
+    return f"({delimiter.join(settings_hex)})"
 
 
 class CC1101:
@@ -193,7 +190,7 @@ class CC1101:
     @property
     def _spi_device_path(self) -> str:
         # https://github.com/doceme/py-spidev/blob/v3.4/spidev_module.c#L1286
-        return "/dev/spidev{}.{}".format(self._spi_bus, self._spi_chip_select)
+        return f"/dev/spidev{self._spi_bus}.{self._spi_chip_select}"
 
     @staticmethod
     def _log_chip_status_byte(chip_status: int) -> None:
@@ -272,7 +269,7 @@ class CC1101:
 
     @classmethod
     def _filter_bandwidth_floating_point_to_real(
-        cls, mantissa: int, exponent: int
+        cls, *, mantissa: int, exponent: int
     ) -> float:
         """
         See "13 Receiver Channel Filter Bandwidth"
@@ -336,7 +333,9 @@ class CC1101:
         )
 
     @classmethod
-    def _symbol_rate_floating_point_to_real(cls, mantissa: int, exponent: int) -> float:
+    def _symbol_rate_floating_point_to_real(
+        cls, *, mantissa: int, exponent: int
+    ) -> float:
         # see "12 Data Rate Programming"
         return (
             (256 + mantissa)
@@ -404,7 +403,7 @@ class CC1101:
         self,
         mode: SyncMode,
         *,
-        _carrier_sense_threshold_enabled: typing.Optional[bool] = None  # unstable
+        _carrier_sense_threshold_enabled: typing.Optional[bool] = None,  # unstable
     ) -> None:
         """
         MDMCFG2.SYNC_MODE
@@ -455,9 +454,9 @@ class CC1101:
         """
         if length < 1:
             raise ValueError(
-                "invalid preamble length {} given".format(length)
-                + "\ncall .set_sync_mode(cc1101.SyncMode.NO_PREAMBLE_AND_SYNC_WORD)"
-                + " to disable preamble"
+                f"invalid preamble length {length} given"
+                "\ncall .set_sync_mode(cc1101.SyncMode.NO_PREAMBLE_AND_SYNC_WORD)"
+                " to disable preamble"
             )
         if length % 3 == 0:
             index = math.log2(length / 3) * 2 + 1
@@ -465,8 +464,8 @@ class CC1101:
             index = math.log2(length / 2) * 2
         if not index.is_integer() or index < 0 or index > 0b111:
             raise ValueError(
-                "unsupported preamble length: {} bytes".format(length)
-                + "\nsee MDMCFG1.NUM_PREAMBLE in cc1101 docs"
+                f"unsupported preamble length: {length} bytes"
+                "\nsee MDMCFG1.NUM_PREAMBLE in cc1101 docs"
             )
         self._set_preamble_length_index(int(index))
 
@@ -504,16 +503,15 @@ class CC1101:
         partnum = self._read_status_register(StatusRegisterAddress.PARTNUM)
         if partnum != self._SUPPORTED_PARTNUM:
             raise ValueError(
-                "unexpected chip part number {} (expected: {})".format(
-                    partnum, self._SUPPORTED_PARTNUM
-                )
+                f"unexpected chip part number {partnum} (expected: {self._SUPPORTED_PARTNUM})"
             )
         version = self._read_status_register(StatusRegisterAddress.VERSION)
         if version not in self._SUPPORTED_VERSIONS:
-            msg = "Unsupported chip version 0x{:02x} (expected one of [{}])".format(
-                version,
-                ", ".join("0x{:02x}".format(v) for v in self._SUPPORTED_VERSIONS),
+            msg = f"Unsupported chip version 0x{version:02x}"
+            supported_versions = ", ".join(
+                f"0x{v:02x}" for v in self._SUPPORTED_VERSIONS
             )
+            msg += f" (expected one of [{supported_versions}])"
             if version == 0:
                 msg += (
                     "\n\nPlease verify that all required pins are connected"
@@ -549,11 +547,11 @@ class CC1101:
             self._spi.open(self._spi_bus, self._spi_chip_select)
         except PermissionError as exc:
             raise PermissionError(
-                "Could not access {}".format(self._spi_device_path)
-                + "\nVerify that the current user has both read and write access."
-                + "\nOn some systems, like Raspberry Pi OS / Raspbian,"
-                + "\n\tsudo usermod -a -G spi $USER"
-                + "\nfollowed by a re-login grants sufficient permissions."
+                f"Could not access {self._spi_device_path}"
+                "\nVerify that the current user has both read and write access."
+                "\nOn some systems, like Raspberry Pi OS / Raspbian,"
+                "\n\tsudo usermod -a -G spi $USER"
+                "\nfollowed by a re-login grants sufficient permissions."
             ) from exc
         if self._lock_spi_device:
             # advisory, exclusive, non-blocking
@@ -566,9 +564,7 @@ class CC1101:
             self._configure_defaults()
             marcstate = self.get_main_radio_control_state_machine_state()
             if marcstate != MainRadioControlStateMachineState.IDLE:
-                raise ValueError(
-                    "expected marcstate idle (actual: {})".format(marcstate.name)
-                )
+                raise ValueError(f"expected marcstate idle (actual: {marcstate.name})")
         except:
             self._spi.close()
             raise
@@ -655,9 +651,8 @@ class CC1101:
             # > can do about the situation, but the event should still be noted.
             # https://docs.python.org/3/howto/logging.html#when-to-use-logging
             warnings.warn(
-                "CC1101 is unable to transmit at frequencies below {:.1f} MHz".format(
-                    self._TRANSMIT_MIN_FREQUENCY_HERTZ / 1e6
-                )
+                "CC1101 is unable to transmit at frequencies"
+                f" below {(self._TRANSMIT_MIN_FREQUENCY_HERTZ / 1e6):.1f} MHz"
             )
         self._set_base_frequency_control_word(
             self._hertz_to_frequency_control_word(freq)
@@ -666,31 +661,27 @@ class CC1101:
     def __str__(self) -> str:
         sync_mode = self.get_sync_mode()
         attrs = (
-            "marcstate={}".format(
-                self.get_main_radio_control_state_machine_state().name.lower()
-            ),
-            "base_frequency={:.2f}MHz".format(
-                self.get_base_frequency_hertz() / 10 ** 6
-            ),
-            "symbol_rate={:.2f}kBaud".format(self.get_symbol_rate_baud() / 1000),
-            "modulation_format={}".format(self.get_modulation_format().name),
-            "sync_mode={}".format(sync_mode.name),
-            "preamble_length={}B".format(self.get_preamble_length_bytes())
+            f"marcstate={self.get_main_radio_control_state_machine_state().name.lower()}",
+            f"base_frequency={(self.get_base_frequency_hertz() / 1e6):.2f}MHz",
+            f"symbol_rate={(self.get_symbol_rate_baud() / 1000):.2f}kBaud",
+            f"modulation_format={self.get_modulation_format().name}",
+            f"sync_mode={sync_mode.name}",
+            f"preamble_length={self.get_preamble_length_bytes()}B"
             if sync_mode != SyncMode.NO_PREAMBLE_AND_SYNC_WORD
             else None,
-            "sync_word=0x{}".format(self.get_sync_word().hex())
+            f"sync_word=0x{self.get_sync_word().hex()}"
             if sync_mode != SyncMode.NO_PREAMBLE_AND_SYNC_WORD
             else None,
-            "packet_length{}{}B".format(
+            "packet_length{}{}B".format(  # pylint: disable=consider-using-f-string
                 "≤"
                 if self.get_packet_length_mode() == PacketLengthMode.VARIABLE
                 else "=",
                 self.get_packet_length_bytes(),
             ),
-            "output_power={}".format(
-                _format_patable(self.get_output_power(), insert_spaces=False)
-            ),
+            "output_power="
+            + _format_patable(self.get_output_power(), insert_spaces=False),
         )
+        # pylint: disable=consider-using-f-string
         return "CC1101({})".format(", ".join(filter(None, attrs)))
 
     def get_configuration_register_values(
@@ -728,7 +719,7 @@ class CC1101:
         See .set_sync_word()
         """
         if len(sync_word) != 2:
-            raise ValueError("expected two bytes, got {!r}".format(sync_word))
+            raise ValueError(f"expected two bytes, got {sync_word!r}")
         self._write_burst(
             start_register=ConfigurationRegisterAddress.SYNC1, values=list(sync_word)
         )
@@ -750,9 +741,7 @@ class CC1101:
         """
         see get_packet_length_bytes()
         """
-        assert 1 <= packet_length <= 255, "unsupported packet length {}".format(
-            packet_length
-        )
+        assert 1 <= packet_length <= 255, f"unsupported packet length {packet_length}"
         self._write_burst(
             start_register=ConfigurationRegisterAddress.PKTLEN, values=[packet_length]
         )
@@ -891,14 +880,12 @@ class CC1101:
         packet_length = self.get_packet_length_bytes()
         if packet_length_mode == PacketLengthMode.VARIABLE:
             if not payload:
-                raise ValueError("empty payload {!r}".format(payload))
+                raise ValueError(f"empty payload {payload!r}")
             if len(payload) > packet_length:
                 raise ValueError(
-                    "payload exceeds maximum payload length of {} bytes".format(
-                        packet_length
-                    )
-                    + "\nsee .get_packet_length_bytes()"
-                    + "\npayload: {!r}".format(payload)
+                    f"payload exceeds maximum payload length of {packet_length} bytes"
+                    "\nsee .get_packet_length_bytes()"
+                    f"\npayload: {payload!r}"
                 )
             payload = int.to_bytes(len(payload), length=1, byteorder="big") + payload
         elif (
@@ -906,18 +893,14 @@ class CC1101:
             and len(payload) != packet_length
         ):
             raise ValueError(
-                "expected payload length of {} bytes, got {}".format(
-                    packet_length, len(payload)
-                )
+                f"expected payload length of {packet_length} bytes, got {len(payload)}"
                 + "\nsee .set_packet_length_mode() and .get_packet_length_bytes()"
-                + "\npayload: {!r}".format(payload)
+                + f"\npayload: {payload!r}"
             )
         marcstate = self.get_main_radio_control_state_machine_state()
         if marcstate != MainRadioControlStateMachineState.IDLE:
             raise Exception(
-                "device must be idle before transmission (current marcstate: {})".format(
-                    marcstate.name
-                )
+                f"device must be idle before transmission (current marcstate: {marcstate.name})"
             )
         self._flush_tx_fifo_buffer()
         self._write_burst(FIFORegisterAddress.TX, list(payload))

+ 4 - 4
cc1101/_cli.py

@@ -76,6 +76,7 @@ def _init_logging(args: argparse.Namespace) -> None:
 
 
 def _configure_via_args(
+    *,
     transceiver: cc1101.CC1101,
     args: argparse.Namespace,
     packet_length_if_fixed: typing.Optional[int],
@@ -134,10 +135,9 @@ def _export_config():
             )
         print("]")
         print(
-            "# PATABLE = {}".format(
-                # pylint: disable=protected-access; internal function & method
-                cc1101._format_patable(transceiver._get_patable(), insert_spaces=True)
-            )
+            # pylint: disable=protected-access; internal function & method
+            "# PATABLE = "
+            + cc1101._format_patable(transceiver._get_patable(), insert_spaces=True)
         )
 
 

+ 13 - 18
cc1101/_gpio.py

@@ -60,31 +60,28 @@ class GPIOLine:
     def find(cls, name: bytes) -> "GPIOLine":
         # > If this routine succeeds, the user must manually close the GPIO chip
         # > owning this line to avoid memory leaks.
-        pointer = _load_libgpiod().gpiod_line_find(name)  # type: int
+        pointer: int = _load_libgpiod().gpiod_line_find(name)
         # > If the line could not be found, this functions sets errno to ENOENT.
         if pointer == 0:
             err = ctypes.get_errno()
             if err == errno.EACCES:
                 # > [PermissionError] corresponds to errno EACCES and EPERM.
                 raise PermissionError(
-                    "Failed to access GPIO line {!r}.".format(name.decode())
-                    + "\nVerify that the current user has read and write access for /dev/gpiochip*."
-                    + "\nOn some systems, like Raspberry Pi OS / Raspbian,"
-                    + "\n\tsudo usermod -a -G gpio $USER"
-                    + "\nfollowed by a re-login grants sufficient permissions."
+                    f"Failed to access GPIO line {name.decode()!r}."
+                    "\nVerify that the current user has read and write access for /dev/gpiochip*."
+                    "\nOn some systems, like Raspberry Pi OS / Raspbian,"
+                    "\n\tsudo usermod -a -G gpio $USER"
+                    "\nfollowed by a re-login grants sufficient permissions."
                 )
             if err == errno.ENOENT:
                 # > [FileNotFoundError] corresponds to errno ENOENT.
                 # https://docs.python.org/3/library/exceptions.html#FileNotFoundError
                 raise FileNotFoundError(
-                    "GPIO line {!r} does not exist.".format(name.decode())
-                    + "\nRun command `gpioinfo` to get a list of all available GPIO lines."
+                    f"GPIO line {name.decode()!r} does not exist."
+                    "\nRun command `gpioinfo` to get a list of all available GPIO lines."
                 )
             raise OSError(
-                "Failed to open GPIO line {!r}: {}".format(
-                    name.decode(),
-                    errno.errorcode[err],
-                )
+                f"Failed to open GPIO line {name.decode()!r}: {errno.errorcode[err]}"
             )
         return cls(pointer=ctypes.c_void_p(pointer))
 
@@ -97,7 +94,7 @@ class GPIOLine:
         self._pointer = None
 
     def wait_for_rising_edge(
-        self, consumer: bytes, timeout: datetime.timedelta
+        self, *, consumer: bytes, timeout: datetime.timedelta
     ) -> bool:
         """
         Return True, if an event occured; False on timeout.
@@ -110,17 +107,15 @@ class GPIOLine:
         ):
             err = ctypes.get_errno()
             raise OSError(
-                "Request for rising edge event notifications failed ({}).".format(
-                    errno.errorcode[err]
-                )
+                f"Request for rising edge event notifications failed ({errno.errorcode[err]})."
                 + ("\nBlocked by another process?" if err == errno.EBUSY else "")
             )
         timeout_timespec = _c_timespec(
             int(timeout.total_seconds()), timeout.microseconds * 1000
         )
-        result = _load_libgpiod().gpiod_line_event_wait(
+        result: int = _load_libgpiod().gpiod_line_event_wait(
             self._pointer, ctypes.pointer(timeout_timespec)
-        )  # type: int
+        )
         _load_libgpiod().gpiod_line_release(self._pointer)
         if result == -1:
             raise OSError("Failed to wait for rising edge event notification.")

+ 3 - 1
setup.py

@@ -50,11 +50,11 @@ 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",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Topic :: Home Automation",
         "Topic :: Communications",
     ],
@@ -64,6 +64,8 @@ setuptools.setup(
             "cc1101-transmit = cc1101._cli:_transmit",
         ]
     },
+    # f-strings, variable type hints, force kwargs with *
+    python_requires=">=3.6",
     install_requires=[
         # apt install python3-spidev
         # https://github.com/doceme/py-spidev

+ 1 - 1
tests/test_cli_export_config.py

@@ -117,7 +117,7 @@ def test_root_log_level(args, root_log_level, log_format):
         "sys.argv", [""] + args
     ), unittest.mock.patch("logging.basicConfig") as logging_basic_config_mock:
         cc1101._cli._export_config()
-    assert logging_basic_config_mock.call_count == 1
+    logging_basic_config_mock.assert_called_once()
     assert logging_basic_config_mock.call_args[1]["level"] == root_log_level
     assert logging_basic_config_mock.call_args[1]["format"] == log_format
 

+ 1 - 1
tests/test_cli_transmit.py

@@ -194,7 +194,7 @@ def test_root_log_level(args, root_log_level, log_format):
         "logging.basicConfig"
     ) as logging_basic_config_mock:
         cc1101._cli._transmit()
-    assert logging_basic_config_mock.call_count == 1
+    logging_basic_config_mock.assert_called_once()
     assert logging_basic_config_mock.call_args[1]["level"] == root_log_level
     assert logging_basic_config_mock.call_args[1]["format"] == log_format
 

+ 1 - 1
tests/test_config.py

@@ -147,7 +147,7 @@ def test_set_base_frequency_hertz_low_warning(transceiver, freq_hz, warn):
     ) as set_control_word_mock:
         with warnings.catch_warnings(record=True) as caught_warnings:
             transceiver.set_base_frequency_hertz(freq_hz)
-    assert set_control_word_mock.call_count == 1
+    set_control_word_mock.assert_called_once()
     if warn:
         assert len(caught_warnings) == 1
         assert (

+ 4 - 7
tests/test_gpio.py

@@ -42,8 +42,7 @@ def test_line_find_permission_denied(libgpiod_mock, name):
     libgpiod_mock.gpiod_line_find.return_value = 0
     ctypes.set_errno(errno.EACCES)
     with pytest.raises(
-        PermissionError,
-        match=r"^Failed to access GPIO line {!r}\.\n".format(re.escape(name)),
+        PermissionError, match=f"^Failed to access GPIO line {re.escape(name)!r}\\.\\n"
     ):
         cc1101._gpio.GPIOLine.find(name.encode())
 
@@ -53,8 +52,7 @@ def test_line_find_non_existing(libgpiod_mock, name):
     libgpiod_mock.gpiod_line_find.return_value = 0
     ctypes.set_errno(errno.ENOENT)
     with pytest.raises(
-        FileNotFoundError,
-        match=r"^GPIO line {!r} does not exist\.\n".format(re.escape(name)),
+        FileNotFoundError, match=f"^GPIO line {re.escape(name)!r} does not exist\\.\\n"
     ):
         cc1101._gpio.GPIOLine.find(name.encode())
 
@@ -64,8 +62,7 @@ def test_line_find_unknown_error(libgpiod_mock, name):
     libgpiod_mock.gpiod_line_find.return_value = 0
     ctypes.set_errno(errno.ENOANO)
     with pytest.raises(
-        OSError,
-        match=r"^Failed to open GPIO line {!r}: ENOANO$".format(re.escape(name)),
+        OSError, match=f"^Failed to open GPIO line {re.escape(name)!r}: ENOANO$"
     ):
         cc1101._gpio.GPIOLine.find(name.encode())
 
@@ -101,7 +98,7 @@ def test_line_wait_for_rising_edge(
     libgpiod_mock.gpiod_line_request_rising_edge_events.assert_called_once_with(
         pointer, consumer
     )
-    assert libgpiod_mock.gpiod_line_event_wait.call_count == 1
+    libgpiod_mock.gpiod_line_event_wait.assert_called_once()
     wait_args, wait_kwargs = libgpiod_mock.gpiod_line_event_wait.call_args
     assert wait_args[0] == pointer
     assert (

+ 1 - 1
tests/test_lock.py

@@ -33,7 +33,7 @@ def spidev_mock(tmp_path):
             self._file = None
 
         def open(self, bus, select):
-            path = tmp_path.joinpath("spidev{}.{}~".format(bus, select))
+            path = tmp_path.joinpath(f"spidev{bus}.{select}~")
             self._file = path.open("w+")
 
         def fileno(self):

+ 3 - 5
tests/test_spi.py

@@ -34,7 +34,7 @@ def test___init__select_device(bus, chip_select):
         transceiver = cc1101.CC1101(spi_bus=bus, spi_chip_select=chip_select)
     assert transceiver._spi_bus == bus
     assert transceiver._spi_chip_select == chip_select
-    assert transceiver._spi_device_path == "/dev/spidev{}.{}".format(bus, chip_select)
+    assert transceiver._spi_device_path == f"/dev/spidev{bus}.{chip_select}"
     transceiver._spi.open.side_effect = SystemExit
     with pytest.raises(SystemExit):
         with transceiver:
@@ -121,7 +121,7 @@ def test___enter___unsupported_chip_version(transceiver):
         }[r]
         with pytest.raises(
             ValueError,
-            match=r"^{}$".format(
+            match=r"^{}$".format(  # pylint: disable=consider-using-f-string
                 re.escape(
                     "Unsupported chip version 0x15 (expected one of [0x04, 0x14])"
                 )
@@ -156,9 +156,7 @@ def test___enter__permission_error(transceiver, bus, chip_select):
     transceiver._spi.open.side_effect = PermissionError("[Errno 13] Permission denied")
     transceiver._spi_bus = bus
     transceiver._spi_chip_select = chip_select
-    with pytest.raises(
-        PermissionError, match=r"\s/dev/spidev{}.{}\s".format(bus, chip_select)
-    ):
+    with pytest.raises(PermissionError, match=f"\\s/dev/spidev{bus}.{chip_select}\\s"):
         with transceiver:
             pass
 

+ 2 - 2
tests/test_transmit.py

@@ -80,8 +80,8 @@ def test_transmit_fixed(caplog, transceiver, payload):
         (
             "cc1101",
             20,
-            "transmitting 0x{} ({!r})".format(
-                "".join("{:02x}".format(b) for b in payload), payload
+            "transmitting 0x{} ({!r})".format(  # pylint: disable=consider-using-f-string
+                "".join(f"{b:02x}" for b in payload), payload
             ),
         )
     ]