Skip to content

Commit

Permalink
Merge branch 'main' into v3.33.4-loop
Browse files Browse the repository at this point in the history
  • Loading branch information
jantman authored Dec 4, 2024
2 parents 7b0254e + cc070d3 commit 1dc6471
Show file tree
Hide file tree
Showing 76 changed files with 4,356 additions and 3,212 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
- '3.6'
- 'pypy3.10'
env:
PYTHON_SLACK_SDK_MOCK_SERVER_MODE: 'threading'
CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: '1'
CI_UNSTABLE_TESTS_SKIP_ENABLED: '1'
FORCE_COLOR: '1'
Expand Down Expand Up @@ -54,8 +53,9 @@ jobs:
python setup.py unit_tests --test-target tests/slack_sdk/oauth/state_store/test_sqlalchemy.py
- name: Run codecov (only 3.9)
if: startsWith(matrix.python-version, '3.9')
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
# python setup.py validate generates the coverage file
files: ./coverage.xml

4,902 changes: 3,480 additions & 1,422 deletions docs/package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@
"write-heading-ids": "docusaurus write-heading-ids"
},
"dependencies": {
"@docusaurus/core": "^3.5.2",
"@docusaurus/plugin-client-redirects": "^3.5.2",
"@docusaurus/preset-classic": "^3.5.2",
"@docusaurus/core": "^3.6.3",
"@docusaurus/plugin-client-redirects": "^3.6.3",
"@docusaurus/preset-classic": "^3.6.3",
"@mdx-js/react": "^3.1.0",
"clsx": "^2.0.0",
"docusaurus-theme-github-codeblock": "^2.0.2",
Expand Down
2 changes: 1 addition & 1 deletion requirements/documentation.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
docutils==0.21.2
pdoc3==0.11.1
pdoc3==0.11.3
2 changes: 1 addition & 1 deletion requirements/optional.txt
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,5 @@ boto3<=2
SQLAlchemy>=1.4,<3
# Socket Mode
# websockets 9 is not compatible with Python 3.10
websockets>=9.1,<14
websockets>=9.1,<15
websocket-client>=1,<2
2 changes: 2 additions & 0 deletions slack_sdk/web/async_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/internal_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def _get_url(base_url: str, api_method: str) -> str:
The absolute API URL.
e.g. 'https://slack.com/api/chat.postMessage'
"""
# Ensure no leading slash in api_method to prevent double slashes
api_method = api_method.lstrip("/")
return urljoin(base_url, api_method)


Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/legacy_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
15 changes: 0 additions & 15 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,5 @@ def restore_os_env(old_env: dict) -> None:
os.environ.update(old_env)


def get_mock_server_mode() -> str:
"""Returns a str representing the mode.
:return: threading/multiprocessing
"""
mode = os.environ.get("PYTHON_SLACK_SDK_MOCK_SERVER_MODE")
if mode is None:
# We used to use "multiprocessing"" for macOS until Big Sur 11.1
# Since 11.1, the "multiprocessing" mode started failing a lot...
# Therefore, we switched the default mode back to "threading".
return "threading"
else:
return mode


def is_ci_unstable_test_skip_enabled() -> bool:
return os.environ.get("CI_UNSTABLE_TESTS_SKIP_ENABLED") == "1"
87 changes: 87 additions & 0 deletions tests/mock_web_api_server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import asyncio
from http.server import SimpleHTTPRequestHandler
from queue import Queue
import threading
import time
from typing import Type
from unittest import TestCase

from tests.mock_web_api_server.received_requests import ReceivedRequests
from tests.mock_web_api_server.mock_server_thread import MockServerThread


def setup_mock_web_api_server(test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888):
test.server_started = threading.Event()
test.received_requests = ReceivedRequests(Queue())
test.thread = MockServerThread(queue=test.received_requests.queue, test=test, handler=handler, port=port)
test.thread.start()
test.server_started.wait()


def cleanup_mock_web_api_server(test: TestCase):
test.thread.stop()
test.thread = None


def assert_received_request_count(test: TestCase, path: str, min_count: int, timeout: float = 1):
start_time = time.time()
error = None
while time.time() - start_time < timeout:
try:
received_count = test.received_requests.get(path, 0)
assert (
received_count == min_count
), f"Expected {min_count} '{path}' {'requests' if min_count > 1 else 'request'}, but got {received_count}!"
return
except Exception as e:
error = e
# waiting for some requests to be received
time.sleep(0.05)

if error is not None:
raise error


def assert_auth_test_count(test: TestCase, expected_count: int):
assert_received_request_count(test, "/auth.test", expected_count, 0.5)


#########
# async #
#########


def setup_mock_web_api_server_async(test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888):
test.server_started = threading.Event()
test.received_requests = ReceivedRequests(asyncio.Queue())
test.thread = MockServerThread(queue=test.received_requests.queue, test=test, handler=handler, port=port)
test.thread.start()
test.server_started.wait()


def cleanup_mock_web_api_server_async(test: TestCase):
test.thread.stop_unsafe()
test.thread = None


async def assert_received_request_count_async(test: TestCase, path: str, min_count: int, timeout: float = 1):
start_time = time.time()
error = None
while time.time() - start_time < timeout:
try:
received_count = await test.received_requests.get_async(path, 0)
assert (
received_count == min_count
), f"Expected {min_count} '{path}' {'requests' if min_count > 1 else 'request'}, but got {received_count}!"
return
except Exception as e:
error = e
# waiting for mock_received_requests updates
await asyncio.sleep(0.05)

if error is not None:
raise error


async def assert_auth_test_count_async(test: TestCase, expected_count: int):
await assert_received_request_count_async(test, "/auth.test", expected_count, 0.5)
41 changes: 41 additions & 0 deletions tests/mock_web_api_server/mock_server_thread.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from asyncio import Queue
import asyncio
from http.server import HTTPServer, SimpleHTTPRequestHandler
import threading
from typing import Type, Union
from unittest import TestCase


class MockServerThread(threading.Thread):
def __init__(
self, queue: Union[Queue, asyncio.Queue], test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888
):
threading.Thread.__init__(self)
self.handler = handler
self.test = test
self.queue = queue
self.port = port

def run(self):
self.server = HTTPServer(("localhost", self.port), self.handler)
self.server.queue = self.queue
self.test.server_url = f"http://localhost:{str(self.port)}"
self.test.host, self.test.port = self.server.socket.getsockname()
self.test.server_started.set() # threading.Event()

self.test = None
try:
self.server.serve_forever(0.05)
finally:
self.server.server_close()

def stop(self):
with self.server.queue.mutex:
del self.server.queue
self.server.shutdown()
self.join()

def stop_unsafe(self):
del self.server.queue
self.server.shutdown()
self.join()
21 changes: 21 additions & 0 deletions tests/mock_web_api_server/received_requests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import asyncio
from queue import Queue
from typing import Optional, Union


class ReceivedRequests:
def __init__(self, queue: Union[Queue, asyncio.Queue]):
self.queue = queue
self.received_requests: dict = {}

def get(self, key: str, default: Optional[int] = None) -> Optional[int]:
while not self.queue.empty():
path = self.queue.get()
self.received_requests[path] = self.received_requests.get(path, 0) + 1
return self.received_requests.get(key, default)

async def get_async(self, key: str, default: Optional[int] = None) -> Optional[int]:
while not self.queue.empty():
path = await self.queue.get()
self.received_requests[path] = self.received_requests.get(path, 0) + 1
return self.received_requests.get(key, default)
Loading

0 comments on commit 1dc6471

Please sign in to comment.