Repository: slackapi/python-slack-sdk
Pull request #914 (2021-01-12 10:06:31)

Add aiohttp version validation (ref #912)
## Summary

This pull request adds aiohttp library validator to prevent the situation like #912 happening to many developers. After merging this PR, I will apply the same validation to Socket Mode client based on aiohttp in #883 

### Category (place an `x` in each of the `[ ]`)

- [ ] **slack_sdk.web.WebClient** (Web API client)
- [ ] **slack_sdk.webhook.WebhookClient** (Incoming Webhook, response_url sender)
- [ ] **slack_sdk.models** (UI component builders)
- [ ] **slack_sdk.oauth** (OAuth Flow Utilities)
- [x] **slack_sdk.rtm.RTMClient** (RTM client)
- [ ] **slack_sdk.signature** (Request Signature Verifier)
- [ ] `/docs-src` (Documents, have you run `./docs.sh`?)
- [ ] `/docs-src-v2` (Documents, have you run `./docs-v2.sh`?)

## Requirements (place an `x` in each `[ ]`)

- [x] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/python-slack-sdk/blob/main/.github/contributing.md) and have done my best effort to follow them.
- [x] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
- [x] I've run `python setup.py validate` after making the changes.


codecov[bot]: # [Codecov](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=h1) Report
> Merging [#914](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=desc) (0d06add) into [main](https://codecov.io/gh/slackapi/python-slack-sdk/commit/e05af1f453101a1e7c8ca69b67705e14eb53b858?el=desc) (e05af1f) will **increase** coverage by `0.00%`.
> The diff coverage is `90.90%`.

[![Impacted file tree graph](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914/graphs/tree.svg?width=650&height=150&src=pr&token=F0O7meYmtD)](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##             main     #914   +/-   ##
=======================================
  Coverage   87.94%   87.95%           
=======================================
  Files          35       36    +1     
  Lines        3385     3396   +11     
=======================================
+ Hits         2977     2987   +10     
- Misses        408      409    +1     
```


| [Impacted Files](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [slack\_sdk/aiohttp\_version\_checker.py](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914/diff?src=pr&el=tree#diff-c2xhY2tfc2RrL2Fpb2h0dHBfdmVyc2lvbl9jaGVja2VyLnB5) | `90.90% <90.90%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=footer). Last update [e05af1f...0d06add](https://codecov.io/gh/slackapi/python-slack-sdk/pull/914?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/setup.py b/setup.py
index eb94561..40150c9 100644
--- a/setup.py
+++ b/setup.py
@@ -295,7 +295,9 @@ setup(
         "optional": [
             # async modules depend on aiohttp
             "aiodns>1.0",
-            "aiohttp>=3,<4",
+            # We recommend using 3.7.1+ for RTMClient
+            # https://github.com/slackapi/python-slack-sdk/issues/912
+            "aiohttp>=3.7.3,<4",
             # used only under slack_sdk/*_store
             "boto3<=2",
             # InstallationStore/OAuthStateStore
diff --git a/slack_sdk/aiohttp_version_checker.py b/slack_sdk/aiohttp_version_checker.py
new file mode 100644
index 0000000..0df1644
--- /dev/null
+++ b/slack_sdk/aiohttp_version_checker.py
@@ -0,0 +1,23 @@
+import logging
+from typing import Callable
+
+
+def _print_warning_log(message: str) -> None:
+    logging.getLogger(__name__).warning(message)
+
+
+def validate_aiohttp_version(
+    aiohttp_version: str, print_warning: Callable[[str], None] = _print_warning_log,
+):
+    if aiohttp_version is not None:
+        elements = aiohttp_version.split(".")
+        if len(elements) >= 3:
+            # patch version can be a non-numeric value
+            major, minor, patch = int(elements[0]), int(elements[1]), elements[2]
+            if major <= 2 or (
+                major == 3 and (minor == 6 or (minor == 7 and patch == "0"))
+            ):
+                print_warning(
+                    "We highly recommend upgrading aiohttp to 3.7.3 or higher versions."
+                    "An older version of the library may not work with the Slack server-side in the future."
+                )
diff --git a/slack_sdk/rtm/__init__.py b/slack_sdk/rtm/__init__.py
index 45a6b97..fca3d71 100644
--- a/slack_sdk/rtm/__init__.py
+++ b/slack_sdk/rtm/__init__.py
@@ -16,9 +16,13 @@ from typing import Optional, Callable, DefaultDict
 import aiohttp
 
 import slack_sdk.errors as client_err
+from slack_sdk.aiohttp_version_checker import validate_aiohttp_version
 from slack_sdk.web.legacy_client import LegacyWebClient as WebClient
 
 
+validate_aiohttp_version(aiohttp.__version__)
+
+
 class RTMClient(object):  # skipcq: PYL-R0205
     """An RTMClient allows apps to communicate with the Slack Platform's RTM API.
 


Test patch:
diff --git a/tests/test_aiohttp_version_checker.py b/tests/test_aiohttp_version_checker.py
new file mode 100644
index 0000000..ee19989
--- /dev/null
+++ b/tests/test_aiohttp_version_checker.py
@@ -0,0 +1,42 @@
+import logging
+import unittest
+
+from slack_sdk.aiohttp_version_checker import validate_aiohttp_version
+
+
+class TestAiohttpVersionChecker(unittest.TestCase):
+    def setUp(self):
+        self.logger = logging.getLogger(__name__)
+
+    def tearDown(self):
+        pass
+
+    def test_not_recommended_versions(self):
+        state = {"counter": 0}
+
+        def print(message: str):
+            state["counter"] = state["counter"] + 1
+
+        validate_aiohttp_version("2.1.3", print)
+        self.assertEqual(state["counter"], 1)
+        validate_aiohttp_version("3.6.3", print)
+        self.assertEqual(state["counter"], 2)
+        validate_aiohttp_version("3.7.0", print)
+        self.assertEqual(state["counter"], 3)
+
+    def test_recommended_versions(self):
+        state = {"counter": 0}
+
+        def print(message: str):
+            state["counter"] = state["counter"] + 1
+
+        validate_aiohttp_version("3.7.1", print)
+        self.assertEqual(state["counter"], 0)
+        validate_aiohttp_version("3.7.3", print)
+        self.assertEqual(state["counter"], 0)
+        validate_aiohttp_version("3.8.0", print)
+        self.assertEqual(state["counter"], 0)
+        validate_aiohttp_version("4.0.0", print)
+        self.assertEqual(state["counter"], 0)
+        validate_aiohttp_version("4.0.0rc1", print)
+        self.assertEqual(state["counter"], 0)