Browse Source

restore compatibility with python3.5

https://github.com/fphammerle/ical2vdir/commit/79f45bd9e072b18cd44f97580f854dc54bf93095
https://github.com/fphammerle/switchbot-mqtt/commit/d723f06739d43aee522042d3b32929f0b1f0b212

https://github.com/fphammerle/ical2vdir/pull/10
Fabian Peter Hammerle 3 years ago
parent
commit
07fed12517
8 changed files with 82 additions and 32 deletions
  1. 28 10
      .github/workflows/python.yml
  2. 1 0
      .gitignore
  3. 6 0
      CHANGELOG.md
  4. 15 1
      Pipfile
  5. 20 9
      Pipfile.lock
  6. 6 6
      systemctl_mqtt/__init__.py
  7. 3 3
      tests/test_dbus.py
  8. 3 3
      tests/test_mqtt.py

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

@@ -1,5 +1,7 @@
 # sync with https://github.com/fphammerle/ical2vdir/blob/master/.github/workflows/python.yml
 
+# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions
+
 # shown in badge
 # https://help.github.com/en/actions/automating-your-workflow-with-github-actions/configuring-a-workflow#adding-a-workflow-status-badge-to-your-repository
 name: tests
@@ -11,7 +13,27 @@ on:
   - cron: '0 20 * * 5'
 
 jobs:
-  build:
+  code-format:
+    runs-on: ubuntu-18.04
+    strategy:
+      matrix:
+        python-version:
+        - 3.8
+    steps:
+    - uses: actions/checkout@v1
+    - uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - run: pip install --upgrade pipenv>=2018.10.9
+    - run: sudo apt-get update
+    # TODO exclude dbus-python from pipenv install
+    - run: sudo apt-get install --yes --no-install-recommends libdbus-1-dev
+    - run: pipenv install --python "$PYTHON_VERSION" --deploy --dev
+      env:
+        PYTHON_VERSION: ${{ matrix.python-version }}
+    - run: pipenv graph
+    - run: pipenv run black --check .
+  tests:
     runs-on: ubuntu-18.04
     strategy:
       matrix:
@@ -26,21 +48,17 @@ jobs:
       with:
         python-version: ${{ matrix.python-version }}
     - run: pip install --upgrade pipenv>=2018.10.9
+    - run: sudo apt-get update
     - run: sudo apt-get install --yes --no-install-recommends libdbus-1-dev
-    - run: pipenv install --deploy --dev
-    # ModuleNotFoundError: No module named 'importlib_metadata'
-    # https://github.com/WanzenBug/pylint-import-requirements/issues/17
-    - run: if python3 -c 'import sys; sys.exit(sys.version_info < (3, 8))'; then
-           pipenv graph;
-           pipenv install --dev importlib-metadata;
-           fi
+    - run: pipenv install --python "$PYTHON_VERSION" --deploy --dev
+      env:
+        PYTHON_VERSION: ${{ matrix.python-version }}
     - run: pipenv graph
+    - run: pipenv run pytest --cov=systemctl_mqtt --cov-report=term-missing --cov-fail-under=100
     - run: pipenv run pylint --load-plugins=pylint_import_requirements systemctl_mqtt
     # https://github.com/PyCQA/pylint/issues/352
     - run: pipenv run pylint tests/*
     - run: pipenv run mypy systemctl_mqtt tests
-    - run: pipenv run pytest --cov=systemctl_mqtt --cov-report=term-missing --cov-fail-under=100
-    - run: pipenv run black --check .
     # >=1.9.0 to detect branch name
     # https://github.com/coveralls-clients/coveralls-python/pull/207
     # https://github.com/coverallsapp/github-action/issues/4#issuecomment-547036866

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@
 .mypy_cache/
 build/
 dist/
+tags

+ 6 - 0
CHANGELOG.md

@@ -5,6 +5,12 @@ 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]
+### Fixed
+- compatibility with python3.5:
+  - replaced [PEP526](https://www.python.org/dev/peps/pep-0526/#abstract)-style variable type hints
+    with [PEP484](https://www.python.org/dev/peps/pep-0484/)-compatible
+  - fixed `AttributeError` due to unavailable `MagicMock.assert_called_once`
+  - fixed `TypeError` when calling `datetime.datetime.isoformat(datespec=…)`
 
 ## [0.1.0] - 2020-06-16
 ### Added

+ 15 - 1
Pipfile

@@ -7,12 +7,26 @@ name = "pypi"
 systemctl-mqtt = {editable = true,path = "."}
 
 [dev-packages]
-black = "==19.10b0"
+# black requires python>=3.6
+# https://github.com/psf/black/commit/e74117f172e29e8a980e2c9de929ad50d3769150#diff-2eeaed663bd0d25b7e608891384b7298R51
+black = {version = "==19.10b0", markers = "python_version >= '3.6'"}
 mypy = "*"
 pylint = "*"
 pylint-import-requirements = "*"
 pytest = "*"
 pytest-cov = "*"
 
+# python3.5 compatibility
+# https://github.com/jaraco/zipp/commit/05a3c52b4d41690e0471a2e283cffb500dc0329a
+zipp = "<2"
+# workaround https://github.com/pytest-dev/pytest/issues/3953
+pathlib2 = {version = "*", markers="python_version < '3.6'"}
+
+# python3.8 compatibility
+# workaround https://github.com/WanzenBug/pylint-import-requirements/issues/17
+importlib-metadata = "*"
+# workaround pipenv unexpectedly adding marker "python_version < '3.8'"
+typed-ast = "*"
+
 [requires]
 python_version = "3"

+ 20 - 9
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "0574fc521848e7aecd1bb8fb168855acbaa1705f4993c34ef5a2eda759d41d68"
+            "sha256": "57f72dc7c2b137c416cb0486c2752a3f1261c1d5893b94d875757bc058b511f8"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -61,6 +61,7 @@
                 "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
             ],
             "index": "pypi",
+            "markers": "python_version >= '3.6'",
             "version": "==19.10b0"
         },
         "click": {
@@ -111,7 +112,7 @@
                 "sha256:0505dd08068cfec00f53a74a0ad927676d7757da81b7436a6eefe4c7cf75c545",
                 "sha256:15ec6c0fd909e893e3a08b3a7c76ecb149122fb14b7efe1199ddd4c7c57ea958"
             ],
-            "markers": "python_version < '3.8'",
+            "index": "pypi",
             "version": "==1.6.1"
         },
         "isort": {
@@ -195,6 +196,15 @@
             ],
             "version": "==20.4"
         },
+        "pathlib2": {
+            "hashes": [
+                "sha256:0ec8205a157c80d7acc301c0b18fbd5d44fe655968f5d947b6ecef5290fc35db",
+                "sha256:6cd9a47b597b37cc57de1c05e56fb1a1c9cc9fab04fe78c29acd090418529868"
+            ],
+            "index": "pypi",
+            "markers": "python_version < '3.6'",
+            "version": "==2.3.5"
+        },
         "pathspec": {
             "hashes": [
                 "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0",
@@ -211,10 +221,10 @@
         },
         "py": {
             "hashes": [
-                "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
-                "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
+                "sha256:a673fa23d7000440cc885c17dbd34fafcb7d7a6e230b29f6766400de36a33c44",
+                "sha256:f3b3a4c36512a4c4f024041ab51866f11761cc169670204b235f6b20523d4e6b"
             ],
-            "version": "==1.8.1"
+            "version": "==1.8.2"
         },
         "pylint": {
             "hashes": [
@@ -319,7 +329,7 @@
                 "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4",
                 "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"
             ],
-            "markers": "implementation_name == 'cpython' and python_version < '3.8'",
+            "index": "pypi",
             "version": "==1.4.1"
         },
         "typing-extensions": {
@@ -345,10 +355,11 @@
         },
         "zipp": {
             "hashes": [
-                "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
-                "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
+                "sha256:c70410551488251b0fee67b460fb9a536af8d6f9f008ad10ac51f615b6a521b1",
+                "sha256:e0d9e63797e483a30d27e09fffd308c59a700d365ec34e93cc100844168bf921"
             ],
-            "version": "==3.1.0"
+            "index": "pypi",
+            "version": "==1.2.0"
         }
     }
 }

+ 6 - 6
systemctl_mqtt/__init__.py

@@ -35,9 +35,9 @@ _SHUTDOWN_DELAY = datetime.timedelta(seconds=4)
 def _get_login_manager() -> dbus.proxies.Interface:
     # https://dbus.freedesktop.org/doc/dbus-python/tutorial.html
     bus = dbus.SystemBus()
-    proxy: dbus.proxies.ProxyObject = bus.get_object(
+    proxy = bus.get_object(
         bus_name="org.freedesktop.login1", object_path="/org/freedesktop/login1"
-    )
+    )  # type: dbus.proxies.ProxyObject
     # https://freedesktop.org/wiki/Software/systemd/logind/
     return dbus.Interface(object=proxy, dbus_interface="org.freedesktop.login1.Manager")
 
@@ -46,10 +46,10 @@ def _schedule_shutdown(action: str) -> None:
     # https://github.com/systemd/systemd/blob/v237/src/systemctl/systemctl.c#L8553
     assert action in ["poweroff", "reboot"], action
     shutdown_datetime = datetime.datetime.now() + _SHUTDOWN_DELAY
+    # datetime.datetime.isoformat(timespec=) not available in python3.5
+    # https://github.com/python/cpython/blob/v3.5.9/Lib/datetime.py#L1552
     _LOGGER.info(
-        "scheduling %s for %s",
-        action,
-        shutdown_datetime.isoformat(sep=" ", timespec="seconds"),
+        "scheduling %s for %s", action, shutdown_datetime.strftime("%Y-%m-%d %H:%M:%S"),
     )
     shutdown_epoch_usec = int(shutdown_datetime.timestamp() * 10 ** 6)
     try:
@@ -85,7 +85,7 @@ _MQTT_TOPIC_SUFFIX_ACTION_MAPPING = {
 class _Settings:
     # pylint: disable=too-few-public-methods
     def __init__(self, mqtt_topic_prefix: str) -> None:
-        self.mqtt_topic_action_mapping: typing.Dict[str, typing.Callable] = {}
+        self.mqtt_topic_action_mapping = {}  # type: typing.Dict[str, typing.Callable]
         for topic_suffix, action in _MQTT_TOPIC_SUFFIX_ACTION_MAPPING.items():
             topic = mqtt_topic_prefix + "/" + topic_suffix
             self.mqtt_topic_action_mapping[topic] = action

+ 3 - 3
tests/test_dbus.py

@@ -44,7 +44,7 @@ def test__schedule_shutdown(action):
         "systemctl_mqtt._get_login_manager", return_value=login_manager_mock
     ):
         systemctl_mqtt._schedule_shutdown(action=action)
-    login_manager_mock.ScheduleShutdown.assert_called_once()
+    assert login_manager_mock.ScheduleShutdown.call_count == 1
     schedule_args, schedule_kwargs = login_manager_mock.ScheduleShutdown.call_args
     assert len(schedule_args) == 2
     assert schedule_args[0] == action
@@ -78,7 +78,7 @@ def test__schedule_shutdown_fail(caplog, action, exception_message, log_message)
         "systemctl_mqtt._get_login_manager", return_value=login_manager_mock
     ), caplog.at_level(logging.DEBUG):
         systemctl_mqtt._schedule_shutdown(action=action)
-    login_manager_mock.ScheduleShutdown.assert_called_once()
+    assert login_manager_mock.ScheduleShutdown.call_count == 1
     assert len(caplog.records) == 2
     assert caplog.records[0].levelno == logging.INFO
     assert caplog.records[0].message.startswith("scheduling {} for ".format(action))
@@ -98,7 +98,7 @@ def test_mqtt_topic_suffix_action_mapping(topic_suffix, expected_action_arg):
         "systemctl_mqtt._get_login_manager", return_value=login_manager_mock
     ):
         action()
-    login_manager_mock.ScheduleShutdown.assert_called_once()
+    assert login_manager_mock.ScheduleShutdown.call_count == 1
     schedule_args, schedule_kwargs = login_manager_mock.ScheduleShutdown.call_args
     assert len(schedule_args) == 2
     assert schedule_args[0] == expected_action_arg

+ 3 - 3
tests/test_mqtt.py

@@ -42,7 +42,7 @@ def test__run(mqtt_host, mqtt_port, mqtt_topic_prefix):
             mqtt_password=None,
             mqtt_topic_prefix=mqtt_topic_prefix,
         )
-    mqtt_client_mock.assert_called_once()
+    assert mqtt_client_mock.call_count == 1  # .assert_called_once requires python>=v3.6
     init_args, init_kwargs = mqtt_client_mock.call_args
     assert not init_args
     assert len(init_kwargs) == 1
@@ -58,7 +58,7 @@ def test__run(mqtt_host, mqtt_port, mqtt_topic_prefix):
         mqtt_topic_prefix + "/poweroff"
     )
     mqtt_client_mock().on_message(mqtt_client_mock(), settings, "message")
-    message_handler_mock.assert_called_once()
+    assert message_handler_mock.call_count == 1
     mqtt_client_mock().loop_forever.assert_called_once_with()
 
 
@@ -78,7 +78,7 @@ def test__run_authentication(
             mqtt_password=mqtt_password,
             mqtt_topic_prefix=mqtt_topic_prefix,
         )
-    mqtt_client_mock.assert_called_once()
+    assert mqtt_client_mock.call_count == 1
     init_args, init_kwargs = mqtt_client_mock.call_args
     assert not init_args
     assert set(init_kwargs.keys()) == {"userdata"}