Browse Source

restore runtime compatibility with python3.5 & test compatibility with python3.5-3.7; configure test pipeline; fix some return type hints

Fabian Peter Hammerle 3 years ago
parent
commit
10dbb85561
11 changed files with 181 additions and 59 deletions
  1. 75 0
      .github/workflows/python.yml
  2. 5 0
      .pylintrc
  3. 5 0
      CHANGELOG.md
  4. 6 0
      Pipfile
  5. 36 33
      Pipfile.lock
  6. 12 7
      location_guessing_game_telegram_bot/__init__.py
  7. 4 0
      mypy.ini
  8. 19 10
      setup.py
  9. 6 1
      tests/conftest.py
  10. 5 2
      tests/test_photo_command.py
  11. 8 6
      tests/test_run.py

+ 75 - 0
.github/workflows/python.yml

@@ -0,0 +1,75 @@
+# 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
+
+on:
+  push:
+  pull_request:
+  schedule:
+  - cron: '0 20 * * 5'
+
+jobs:
+  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==2020.8.13
+    - 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:
+        python-version:
+        - 3.5
+        - 3.6
+        - 3.7
+        - 3.8
+        - 3.9
+      fail-fast: false
+    steps:
+    - uses: actions/checkout@v2.3.4
+      with:
+        lfs: true # tests/resources/
+    - uses: actions/setup-python@v1
+      with:
+        python-version: ${{ matrix.python-version }}
+    - run: pip install --upgrade pipenv==2020.8.13
+    - 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=77
+    # 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)"
+    # https://github.com/PyCQA/pylint/issues/352
+    # disable parse-error due to:
+    # > tests/resources/__init__.py:1:0: F0010: error while code parsing: Unable to load file tests/resources/__init__.py:
+    # > [Errno 2] No such file or directory: 'tests/resources/__init__.py' (parse-error)
+    - run: pipenv run pylint --disable=parse-error tests/*
+    - run: pipenv run mypy "$(cat *.egg-info/top_level.txt)" tests
+    # >=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.11.0 https://github.com/coveralls-clients/coveralls-python/issues/219
+    - run: pip install 'coveralls>=1.9.0,<2,!=1.11.0'
+    # https://github.com/coverallsapp/github-action/issues/30
+    # https://github.com/coverallsapp/github-action/issues/4#issuecomment-529399410
+    - run: coveralls
+      env:
+        COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}

+ 5 - 0
.pylintrc

@@ -0,0 +1,5 @@
+[MESSAGE CONTROL]
+
+disable=bad-continuation, # black
+        missing-function-docstring,
+        missing-module-docstring

+ 5 - 0
CHANGELOG.md

@@ -5,6 +5,11 @@ 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
+- loosen version constraint for `python-telegram-bot` to restore compatibility with python3.5
+- fixed return type hints in class `_Persistence`
+- added assertions fixing `mypy` errors
+- tests: fixed compatibility with python3.5-3.7
 
 ## [0.1.0] - 2021-02-14
 ### Added

+ 6 - 0
Pipfile

@@ -6,6 +6,12 @@ name = "pypi"
 [packages]
 location-guessing-game-telegram-bot = {path = ".", editable = true}
 
+# python3.5 compatibility
+# https://github.com/python-telegram-bot/python-telegram-bot/commit/19a4f9e53a1798b886fd4ce3e5a9a48db9ae5152#diff-60f61ab7a8d1910d86d9fda2261620314edcae5894d5aaa236b821c7256badd7L64
+python-telegram-bot = "<13"
+# https://github.com/pyca/cryptography/commit/d9e174d3e16c4dc50789f9897334bcc14d0b05d9
+cryptography = "<3.3"
+
 [dev-packages]
 # black requires python>=3.6
 # https://github.com/psf/black/commit/e74117f172e29e8a980e2c9de929ad50d3769150#diff-2eeaed663bd0d25b7e608891384b7298R51

+ 36 - 33
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "0f2a60732f30fd6ceba7215cb24f8b0f75fe93f9488226203a31ab09b3124052"
+            "sha256": "86953d68109f447368f3bd1e152da4d1db6e0c3edd6ef6b7167d62347841c005"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -16,13 +16,6 @@
         ]
     },
     "default": {
-        "apscheduler": {
-            "hashes": [
-                "sha256:3bb5229eed6fbbdafc13ce962712ae66e175aa214c69bed35a06bffcf0c5e244",
-                "sha256:e8b1ecdb4c7cb2818913f766d5898183c7cb8936680710a4d3a966e02262e526"
-            ],
-            "version": "==3.6.3"
-        },
         "certifi": {
             "hashes": [
                 "sha256:1a4995114262bffbc2413b159f2a1a480c969de6e6eb13ee966d470af86af59c",
@@ -74,15 +67,38 @@
         },
         "cryptography": {
             "hashes": [
-                "sha256:0bf49d5b38e4f3745a0eab0597fa97720dd49b30d65f534b49a82e303f149deb",
-                "sha256:18d6f3ac1da14a01c95f4590ee58e96aa6628d231ce738e9eca330b9997127b6",
-                "sha256:4f6761a82b51fe02cda8f45af1c2f698a10f50003dc9c2572d8a49eda2e6d35b",
-                "sha256:8b3b79af57e12aabbc3db81e563eaa07870293a1ffdcc891d107035ce9a0dbff",
-                "sha256:9c6f7552d4f2130542d488b9d9e5b1546204b5d1aa90c823d50cce8eed421363",
-                "sha256:b0873ac0c0e6bc6882cd285930cc382ec4e78786be71bdc113c06246eea61294",
-                "sha256:c8dc9859c5a046e1ca22da360dfd609c09064a4f974881cb5cba977c581088ec"
+                "sha256:07ca431b788249af92764e3be9a488aa1d39a0bc3be313d826bbec690417e538",
+                "sha256:13b88a0bd044b4eae1ef40e265d006e34dbcde0c2f1e15eb9896501b2d8f6c6f",
+                "sha256:32434673d8505b42c0de4de86da8c1620651abd24afe91ae0335597683ed1b77",
+                "sha256:3cd75a683b15576cfc822c7c5742b3276e50b21a06672dc3a800a2d5da4ecd1b",
+                "sha256:4e7268a0ca14536fecfdf2b00297d4e407da904718658c1ff1961c713f90fd33",
+                "sha256:545a8550782dda68f8cdc75a6e3bf252017aa8f75f19f5a9ca940772fc0cb56e",
+                "sha256:55d0b896631412b6f0c7de56e12eb3e261ac347fbaa5d5e705291a9016e5f8cb",
+                "sha256:5849d59358547bf789ee7e0d7a9036b2d29e9a4ddf1ce5e06bb45634f995c53e",
+                "sha256:6dc59630ecce8c1f558277ceb212c751d6730bd12c80ea96b4ac65637c4f55e7",
+                "sha256:7117319b44ed1842c617d0a452383a5a052ec6aa726dfbaffa8b94c910444297",
+                "sha256:75e8e6684cf0034f6bf2a97095cb95f81537b12b36a8fedf06e73050bb171c2d",
+                "sha256:7b8d9d8d3a9bd240f453342981f765346c87ade811519f98664519696f8e6ab7",
+                "sha256:a035a10686532b0587d58a606004aa20ad895c60c4d029afa245802347fab57b",
+                "sha256:a4e27ed0b2504195f855b52052eadcc9795c59909c9d84314c5408687f933fc7",
+                "sha256:a733671100cd26d816eed39507e585c156e4498293a907029969234e5e634bc4",
+                "sha256:a75f306a16d9f9afebfbedc41c8c2351d8e61e818ba6b4c40815e2b5740bb6b8",
+                "sha256:bd717aa029217b8ef94a7d21632a3bb5a4e7218a4513d2521c2a2fd63011e98b",
+                "sha256:d25cecbac20713a7c3bc544372d42d8eafa89799f492a43b79e1dfd650484851",
+                "sha256:d26a2557d8f9122f9bf445fc7034242f4375bd4e95ecda007667540270965b13",
+                "sha256:d3545829ab42a66b84a9aaabf216a4dce7f16dbc76eb69be5c302ed6b8f4a29b",
+                "sha256:d3d5e10be0cf2a12214ddee45c6bd203dab435e3d83b4560c03066eda600bfe3",
+                "sha256:efe15aca4f64f3a7ea0c09c87826490e50ed166ce67368a68f315ea0807a20df"
+            ],
+            "index": "pypi",
+            "version": "==3.2.1"
+        },
+        "decorator": {
+            "hashes": [
+                "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760",
+                "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7"
             ],
-            "version": "==3.4.5"
+            "version": "==4.4.2"
         },
         "location-guessing-game-telegram-bot": {
             "editable": true,
@@ -97,17 +113,11 @@
         },
         "python-telegram-bot": {
             "hashes": [
-                "sha256:12adf52b59d7b23ba26b1f3e25ca95a4f850985ebf08a019bb7fd835c432929b",
-                "sha256:b87cdb2fca2983d4d49287c76ea64f13c5b786260bb742fe9a28334f20e6b2d3"
+                "sha256:327186c56469216207dcdf8706892e58e0a62e51ef46f5143268e387bbb4edc3",
+                "sha256:7eebed539ccacf77896cff9e41d1f68746b8ff3ca4da1e2e59285e9c749cb050"
             ],
-            "version": "==13.2"
-        },
-        "pytz": {
-            "hashes": [
-                "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da",
-                "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"
-            ],
-            "version": "==2021.1"
+            "index": "pypi",
+            "version": "==12.8"
         },
         "six": {
             "hashes": [
@@ -161,13 +171,6 @@
                 "sha256:fba85b6cd9c39be262fcd23865652920832b61583de2a2ca907dbd8e8a8c81e5"
             ],
             "version": "==6.1"
-        },
-        "tzlocal": {
-            "hashes": [
-                "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44",
-                "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4"
-            ],
-            "version": "==2.1"
         }
     },
     "develop": {

+ 12 - 7
location_guessing_game_telegram_bot/__init__.py

@@ -61,14 +61,18 @@ def _photo_command(
     update: telegram.update.Update,
     context: telegram.ext.callbackcontext.CallbackContext,
 ):
+    assert isinstance(context.chat_data, dict)  # mypy
+    assert update.effective_chat is not None  # mypy
     if "last_photo_message_id" in context.chat_data:
         update.effective_chat.send_message(
             text="Lösung: {}".format(context.chat_data["last_photo"].description_url),
             disable_web_page_preview=True,
             reply_to_message_id=context.chat_data["last_photo_message_id"],
         )
-        # https://github.com/python-telegram-bot/python-telegram-bot/pull/2043
-        update.effective_chat.send_location(
+        # telegram.chat.Chat.send_location shortcut added in v13.0
+        # https://github.com/python-telegram-bot/python-telegram-bot/commit/fc5844c13da3b3fb20bb2d0bfcdf1efb1a826ba6#diff-2590f2bde47ea3730442f14a3a029ef77d8f2c8f3186cf5edd7e18bcc7243c39R381
+        context.bot.send_location(
+            chat_id=update.effective_chat.id,
             latitude=context.chat_data["last_photo"].latitude,
             longitude=context.chat_data["last_photo"].longitude,
             disable_notification=True,
@@ -83,7 +87,8 @@ def _photo_command(
         try:
             with urllib.request.urlopen(photo.photo_url) as photo_response:
                 photo_message = update.effective_chat.send_photo(
-                    photo=photo_response, caption="Wo wurde dieses Photo aufgenommen?",
+                    photo=photo_response,
+                    caption="Wo wurde dieses Photo aufgenommen?",
                 )
         except telegram.error.BadRequest:
             _LOGGER.warning("file size limit exceeded?", exc_info=True)
@@ -108,11 +113,11 @@ class _Persistence(telegram.ext.BasePersistence):
             store_bot_data=True, store_chat_data=False, store_user_data=False
         )
 
-    def get_user_data(self) -> dict:
-        return {}  # pragma: no cover
+    def get_user_data(self) -> typing.DefaultDict[int, dict]:
+        raise NotImplementedError()  # pragma: no cover
 
-    def get_chat_data(self) -> dict:
-        return {}  # pragma: no cover
+    def get_chat_data(self) -> typing.DefaultDict[int, dict]:
+        raise NotImplementedError()  # pragma: no cover
 
     def get_bot_data(self) -> dict:
         return self._bot_data

+ 4 - 0
mypy.ini

@@ -0,0 +1,4 @@
+[mypy]
+[mypy-telegram.*]
+# no longer required in python-telegram-bot>=13 ?
+ignore_missing_imports = True

+ 19 - 10
setup.py

@@ -33,7 +33,14 @@ setuptools.setup(
     url=_REPO_URL,
     project_urls={"Changelog": _REPO_URL + "/blob/master/CHANGELOG.md"},
     license="GPLv3+",
-    keywords=["bot", "game", "guessing", "location", "photos", "telegram",],
+    keywords=[
+        "bot",
+        "game",
+        "guessing",
+        "location",
+        "photos",
+        "telegram",
+    ],
     classifiers=[
         # https://pypi.org/classifiers/
         "Development Status :: 3 - Alpha",
@@ -41,12 +48,12 @@ setuptools.setup(
         "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
         "Operating System :: OS Independent",
         "Topic :: Games/Entertainment",
-        # .github/workflows/python.yml TODO
-        # "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",
+        # .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",
     ],
     entry_points={
         "console_scripts": [
@@ -54,10 +61,12 @@ setuptools.setup(
         ]
     },
     install_requires=[
-        # >=13.0 telegram.chat.Chat.send_location shortcut
+        # >=13.0 provides telegram.chat.Chat.send_location shortcut
         # https://github.com/python-telegram-bot/python-telegram-bot/commit/fc5844c13da3b3fb20bb2d0bfcdf1efb1a826ba6#diff-2590f2bde47ea3730442f14a3a029ef77d8f2c8f3186cf5edd7e18bcc7243c39R381
-        "python-telegram-bot >= 13.0"
+        # however, >=13.0 requires python>=3.6
+        # https://github.com/python-telegram-bot/python-telegram-bot/commit/19a4f9e53a1798b886fd4ce3e5a9a48db9ae5152#diff-60f61ab7a8d1910d86d9fda2261620314edcae5894d5aaa236b821c7256badd7L64
+        "python-telegram-bot"
     ],
     setup_requires=["setuptools_scm"],
-    # tests_require=["pytest"], TODO
+    tests_require=["pytest"],
 )

+ 6 - 1
tests/conftest.py

@@ -38,7 +38,12 @@ def wikimap_export_path() -> pathlib.Path:
 
 @pytest.fixture(scope="session")
 def wikimap_export(wikimap_export_path) -> pathlib.Path:
-    return json.loads(wikimap_export_path.read_text())
+    try:
+        return json.loads(wikimap_export_path.read_text())
+    except json.decoder.JSONDecodeError as exc:
+        if "git-lfs.github.com" in wikimap_export_path.read_text():
+            raise ValueError("git-lfs pointers unresolved") from exc
+        raise
 
 
 @pytest.fixture(scope="session")

+ 5 - 2
tests/test_photo_command.py

@@ -62,6 +62,7 @@ def test_send_solution_and_next_photo(caplog, wikimap_photos):
     update_mock.effective_chat.send_photo.return_value.message_id = (
         "second photo message id"
     )
+    update_mock.effective_chat.id = "chat id for send_location"
     context_mock = unittest.mock.MagicMock()
     context_mock.bot_data = {"photos": wikimap_photos[1:2]}
     context_mock.chat_data = {
@@ -82,8 +83,10 @@ def test_send_solution_and_next_photo(caplog, wikimap_photos):
         disable_web_page_preview=True,
         reply_to_message_id="first photo message id",
     )
-    update_mock.effective_chat.send_location.assert_called_once_with(
-        # float comparison? :O
+    # update_mock.effective_chat.send_location.assert_called_once_with(
+    context_mock.bot.send_location.assert_called_once_with(
+        chat_id="chat id for send_location",
+        # float comparison? :o
         latitude=47.288805,
         longitude=12.144116,
         disable_notification=True,

+ 8 - 6
tests/test_run.py

@@ -29,16 +29,18 @@ def test__run(tmp_path, wikimap_export_path):
             wikimap_export_path=wikimap_export_path,
         )
     assert updater_mock.call_count == 1
-    assert not updater_mock.call_args.args
-    assert set(updater_mock.call_args.kwargs.keys()) == {
+    # > Changed in version 3.8: Added args and kwargs properties.
+    # https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.call_args_list
+    init_args, init_kwargs = updater_mock.call_args
+    assert not init_args
+    assert set(init_kwargs.keys()) == {
         "persistence",
         "token",
         "use_context",
     }
-    assert updater_mock.call_args.kwargs["token"] == "secret"
-    assert updater_mock.call_args.kwargs["use_context"] is True
-    persistence = updater_mock.call_args.kwargs["persistence"]
-    photos = persistence.get_bot_data()["photos"]
+    assert init_kwargs["token"] == "secret"
+    assert init_kwargs["use_context"] is True
+    photos = init_kwargs["persistence"].get_bot_data()["photos"]
     assert len(photos) == 25
     assert (
         photos[1].photo_url