Skip to content

Commit f67fa40

Browse files
authored
Merge pull request #621 from ably/fix/fix-tests-update-ci
chore: update CI / fix tests
2 parents b5a563d + 30fdc5d commit f67fa40

File tree

13 files changed

+338
-172
lines changed

13 files changed

+338
-172
lines changed

.github/workflows/check.yml

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,42 @@ jobs:
2020
matrix:
2121
python-version: ['3.7', '3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
2222
steps:
23-
- uses: actions/checkout@v2
24-
with:
25-
submodules: 'recursive'
26-
- name: Set up Python ${{ matrix.python-version }}
27-
uses: actions/setup-python@v2
28-
with:
29-
python-version: ${{ matrix.python-version }}
30-
- name: Setup poetry
31-
uses: abatilo/[email protected]
32-
with:
33-
poetry-version: 1.3.2
34-
- name: Install dependencies
35-
run: poetry install -E crypto
36-
- name: Generate rest sync code and tests
37-
run: poetry run unasync
38-
- name: Test with pytest
39-
run: poetry run pytest --verbose --tb=short
23+
- uses: actions/checkout@v4
24+
with:
25+
submodules: 'recursive'
26+
- name: Set up Python ${{ matrix.python-version }}
27+
uses: actions/setup-python@v5
28+
id: setup-python
29+
with:
30+
python-version: ${{ matrix.python-version }}
31+
32+
- name: Setup poetry
33+
uses: abatilo/actions-poetry@v4
34+
with:
35+
poetry-version: '2.1.4'
36+
37+
- name: Setup a local virtual environment
38+
run: |
39+
poetry env use ${{ steps.setup-python.outputs.python-path }}
40+
poetry run python --version
41+
poetry config virtualenvs.create true --local
42+
poetry config virtualenvs.in-project true --local
43+
44+
- uses: actions/cache@v4
45+
name: Define a cache for the virtual environment based on the dependencies lock file
46+
id: cache
47+
with:
48+
path: ./.venv
49+
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('poetry.lock') }}
50+
51+
- name: Ensure cache is healthy
52+
if: steps.cache.outputs.cache-hit == 'true'
53+
shell: bash
54+
run: poetry run pip --version >/dev/null 2>&1 || (echo "Cache is broken, skip it" && rm -rf .venv)
55+
56+
- name: Install dependencies
57+
run: poetry install -E crypto
58+
- name: Generate rest sync code and tests
59+
run: poetry run unasync
60+
- name: Test with pytest
61+
run: poetry run pytest --verbose --tb=short --reruns 3

.github/workflows/lint.yml

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,40 @@ jobs:
1010
lint:
1111
runs-on: ubuntu-latest
1212
steps:
13-
- uses: actions/checkout@v2
14-
with:
15-
submodules: 'recursive'
16-
- name: Set up Python ${{ matrix.python-version }}
17-
uses: actions/setup-python@v2
18-
with:
19-
python-version: '3.8'
20-
- name: Setup poetry
21-
uses: abatilo/[email protected]
22-
with:
23-
poetry-version: 1.3.2
24-
- name: Install dependencies
25-
run: poetry install -E crypto
26-
- name: Lint with flake8
27-
run: poetry run flake8
13+
- uses: actions/checkout@v4
14+
with:
15+
submodules: 'recursive'
16+
- name: Set up Python 3.9
17+
uses: actions/setup-python@v5
18+
id: setup-python
19+
with:
20+
python-version: '3.9'
21+
22+
- name: Setup poetry
23+
uses: abatilo/actions-poetry@v4
24+
with:
25+
poetry-version: '2.1.4'
26+
27+
- name: Setup a local virtual environment
28+
run: |
29+
poetry env use ${{ steps.setup-python.outputs.python-path }}
30+
poetry run python --version
31+
poetry config virtualenvs.create true --local
32+
poetry config virtualenvs.in-project true --local
33+
34+
- uses: actions/cache@v4
35+
name: Define a cache for the virtual environment based on the dependencies lock file
36+
id: cache
37+
with:
38+
path: ./.venv
39+
key: venv-${{ runner.os }}-3.9-${{ hashFiles('poetry.lock') }}
40+
41+
- name: Ensure cache is healthy
42+
if: steps.cache.outputs.cache-hit == 'true'
43+
shell: bash
44+
run: poetry run pip --version >/dev/null 2>&1 || (echo "Cache is broken, skip it." && rm -rf .venv)
45+
46+
- name: Install dependencies
47+
run: poetry install
48+
- name: Lint with flake8
49+
run: poetry run flake8

ably/http/http.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ably.http.httputils import HttpUtils
1212
from ably.transport.defaults import Defaults
1313
from ably.util.exceptions import AblyException
14-
from ably.util.helper import is_token_error
14+
from ably.util.helper import is_token_error, extract_url_params
1515

1616
log = logging.getLogger(__name__)
1717

@@ -198,11 +198,13 @@ def should_stop_retrying():
198198
self.preferred_port)
199199
url = urljoin(base_url, path)
200200

201+
(clean_url, url_params) = extract_url_params(url)
202+
201203
request = self.__client.build_request(
202204
method=method,
203-
url=url,
205+
url=clean_url,
204206
content=body,
205-
params=params,
207+
params=dict(url_params, **params),
206208
headers=all_headers,
207209
timeout=timeout,
208210
)

ably/rest/auth.py

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
from __future__ import annotations
2+
23
import base64
3-
from datetime import timedelta
44
import logging
55
import time
6-
from typing import Optional, TYPE_CHECKING, Union
76
import uuid
7+
from datetime import timedelta
8+
from typing import Optional, TYPE_CHECKING, Union
9+
810
import httpx
911

1012
from ably.types.options import Options
13+
1114
if TYPE_CHECKING:
1215
from ably.rest.rest import AblyRest
1316
from ably.realtime.realtime import AblyRealtime
@@ -16,14 +19,14 @@
1619
from ably.types.tokendetails import TokenDetails
1720
from ably.types.tokenrequest import TokenRequest
1821
from ably.util.exceptions import AblyAuthException, AblyException, IncompatibleClientIdException
22+
from ably.util.helper import extract_url_params
1923

2024
__all__ = ["Auth"]
2125

2226
log = logging.getLogger(__name__)
2327

2428

2529
class Auth:
26-
2730
class Method:
2831
BASIC = "BASIC"
2932
TOKEN = "TOKEN"
@@ -271,8 +274,7 @@ async def create_token_request(self, token_params: Optional[dict | str] = None,
271274
if capability is not None:
272275
token_request['capability'] = str(Capability(capability))
273276

274-
token_request["client_id"] = (
275-
token_params.get('client_id') or self.client_id)
277+
token_request["client_id"] = token_params.get('client_id') or self.client_id
276278

277279
# Note: There is no expectation that the client
278280
# specifies the nonce; this is done by the library
@@ -388,17 +390,27 @@ def _random_nonce(self):
388390

389391
async def token_request_from_auth_url(self, method: str, url: str, token_params,
390392
headers, auth_params):
393+
# Extract URL parameters using utility function
394+
clean_url, url_params = extract_url_params(url)
395+
391396
body = None
392397
params = None
393398
if method == 'GET':
394399
body = {}
395-
params = dict(auth_params, **token_params)
400+
# Merge URL params, auth_params, and token_params (later params override earlier ones)
401+
# we do this because httpx version has inconsistency and some versions override query params
402+
# that are specified in url string
403+
params = {**url_params, **auth_params, **token_params}
396404
elif method == 'POST':
397405
if isinstance(auth_params, TokenDetails):
398406
auth_params = auth_params.to_dict()
399-
params = {}
407+
# For POST, URL params go in query string, auth_params and token_params go in body
408+
params = url_params
400409
body = dict(auth_params, **token_params)
401410

411+
# Use clean URL for the request
412+
url = clean_url
413+
402414
from ably.http.http import Response
403415
async with httpx.AsyncClient(http2=True) as client:
404416
resp = await client.request(method=method, url=url, headers=headers, params=params, data=body)
@@ -420,6 +432,6 @@ async def token_request_from_auth_url(self, method: str, url: str, token_params,
420432
token_request = response.text
421433
else:
422434
msg = 'auth_url responded with unacceptable content-type ' + content_type + \
423-
', should be either text/plain, application/jwt or application/json',
435+
', should be either text/plain, application/jwt or application/json',
424436
raise AblyAuthException(msg, 401, 40170)
425437
return token_request

ably/util/helper.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
import string
44
import asyncio
55
import time
6-
from typing import Callable
6+
from typing import Callable, Tuple, Dict
7+
from urllib.parse import urlparse, parse_qs
78

89

910
def get_random_id():
@@ -25,6 +26,34 @@ def is_token_error(exception):
2526
return 40140 <= exception.code < 40150
2627

2728

29+
def extract_url_params(url: str) -> Tuple[str, Dict[str, str]]:
30+
"""
31+
Extract URL parameters from a URL and return a clean URL and parameters dict.
32+
33+
Args:
34+
url: The URL to parse
35+
36+
Returns:
37+
Tuple of (clean_url_without_params, url_params_dict)
38+
"""
39+
parsed_url = urlparse(url)
40+
url_params = {}
41+
42+
if parsed_url.query:
43+
# Convert query parameters to a flat dictionary
44+
query_params = parse_qs(parsed_url.query)
45+
for key, values in query_params.items():
46+
# Take the last value if multiple values exist for the same key
47+
url_params[key] = values[-1]
48+
49+
# Reconstruct clean URL without query parameters
50+
clean_url = f"{parsed_url.scheme}://{parsed_url.netloc}{parsed_url.path}"
51+
if parsed_url.fragment:
52+
clean_url += f"#{parsed_url.fragment}"
53+
54+
return clean_url, url_params
55+
56+
2857
class Timer:
2958
def __init__(self, timeout: float, callback: Callable):
3059
self._timeout = timeout

0 commit comments

Comments
 (0)