Skip to content

Commit 2d4a7e2

Browse files
feat: add support for Python 3.9, 3.10 and 3.11 (#553)
| Q | A | ----------------- | ---------- | Bug fix? | yes | New feature? | no <!-- please update the /CHANGELOG.md file --> | BC breaks? | no | Related Issue | Fix #549, Fix #551 | Need Doc update | yes ## Describe your change This PR removes support for python 2.7, adds support for python 3.11 and tests the project against 3.9, 3.10, and 3.11: - Replaced all @asyncio.coroutine decorators by async def function def - Replaced `yield from` with `await` or `return` statements - Added python 3.9, 3.10, and 3.11 to tox and circleci ## What problem is this fixing? Due to a removed syntax in python 3.11, the package was not working. --------- Co-authored-by: shortcuts <[email protected]>
1 parent 545e814 commit 2d4a7e2

18 files changed

+146
-135
lines changed

.circleci/config.yml

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ version: 2.1
33
jobs:
44
test:
55
docker:
6-
- image: circleci/python:<< parameters.docker_version >>
6+
- image: << parameters.docker_version >>
77
description: Run tests for Python << parameters.tox_version >>
88
parameters:
99
docker_version:
@@ -12,49 +12,65 @@ jobs:
1212
type: string
1313
steps:
1414
- checkout
15-
- run:
16-
command: sudo pip install --upgrade pip
17-
- run:
18-
command: sudo pip install tox mock
15+
- when:
16+
condition:
17+
matches:
18+
pattern: ".*circleci.*"
19+
value: << parameters.docker_version >>
20+
steps:
21+
- run:
22+
command: sudo pip install --upgrade pip
23+
- run:
24+
command: sudo pip install tox mock
25+
- when:
26+
condition:
27+
matches:
28+
pattern: ".*cimg.*"
29+
value: << parameters.docker_version >>
30+
steps:
31+
- run:
32+
command: pip install --upgrade pip
33+
- run:
34+
command: pip install tox mock
1935
- run:
2036
command: tox -e py<< parameters.tox_version >>-sync,py<< parameters.tox_version >>-async
2137

2238
format:
2339
docker:
24-
- image: circleci/python:3.8.2
40+
- image: cimg/python:3.8.2
2541
description: Run formatting and linting checks
2642
steps:
2743
- checkout
2844
- run:
29-
command: sudo pip install --upgrade pip
45+
command: pip install --upgrade pip
3046
- run:
31-
command: sudo pip install tox
47+
command: pip install tox
3248
- run:
3349
command: tox -e format
3450

3551
types:
3652
docker:
37-
- image: circleci/python:3.8.2
53+
- image: cimg/python:3.8.2
3854
description: Run optional type checking with MyPy
3955
steps:
4056
- checkout
4157
- run:
42-
command: sudo pip install --upgrade pip
58+
command: pip install --upgrade pip
4359
- run:
44-
command: sudo pip install tox
60+
command: pip install tox
4561
- run:
4662
command: tox -e types
4763

4864
release:
4965
docker:
50-
- image: circleci/python:3.8.2
66+
- image: cimg/python:3.8.2
5167
description: Perform a new release of the Python client
5268
steps:
5369
- checkout
5470
- run:
55-
command: sudo pip install --upgrade pip
71+
command: pip install --upgrade pip
5672
- run:
57-
command: sudo pip install tox
73+
command: pip install tox
5874
- run:
5975
command: |
6076
if [[ -z "$PYPI_USER" ]]; then echo '$PYPI_USER is not set'; exit 1; fi
@@ -65,48 +81,62 @@ workflows:
6581
version: 2
6682
build:
6783
jobs:
68-
- test:
69-
name: 'test_27'
70-
docker_version: '2.7'
71-
tox_version: '27'
72-
filters:
73-
tags:
74-
only: /.*/
7584
- test:
7685
name: 'test_34'
77-
docker_version: '3.4'
86+
docker_version: 'circleci/python:3.4'
7887
tox_version: '34'
7988
filters:
8089
tags:
8190
only: /.*/
8291
- test:
8392
name: 'test_35'
84-
docker_version: '3.5'
93+
docker_version: 'cimg/python:3.5'
8594
tox_version: '35'
8695
filters:
8796
tags:
8897
only: /.*/
8998
- test:
9099
name: 'test_36'
91-
docker_version: '3.6'
100+
docker_version: 'cimg/python:3.6'
92101
tox_version: '36'
93102
filters:
94103
tags:
95104
only: /.*/
96105
- test:
97106
name: 'test_37'
98-
docker_version: '3.7'
107+
docker_version: 'cimg/python:3.7'
99108
tox_version: '37'
100109
filters:
101110
tags:
102111
only: /.*/
103112
- test:
104113
name: 'test_38'
105-
docker_version: '3.8'
114+
docker_version: 'cimg/python:3.8'
106115
tox_version: '38'
107116
filters:
108117
tags:
109118
only: /.*/
119+
- test:
120+
name: 'test_39'
121+
docker_version: 'cimg/python:3.9'
122+
tox_version: '39'
123+
filters:
124+
tags:
125+
only: /.*/
126+
- test:
127+
name: 'test_310'
128+
docker_version: 'cimg/python:3.10'
129+
tox_version: '310'
130+
filters:
131+
tags:
132+
only: /.*/
133+
- test:
134+
name: 'test_311'
135+
docker_version: 'cimg/python:3.11.1'
136+
tox_version: '311'
137+
filters:
138+
tags:
139+
only: /.*/
110140
- format:
111141
name: 'format'
112142
filters:
@@ -119,12 +149,14 @@ workflows:
119149
only: /.*/
120150
- release:
121151
requires:
122-
- test_27
123152
- test_34
124153
- test_35
125154
- test_36
126155
- test_37
127156
- test_38
157+
- test_39
158+
- test_310
159+
- test_311
128160
- format
129161
- types
130162
filters:

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
99

1010
## [Unreleased](https://github.com/algolia/algoliasearch-client-python/compare/v2.6.3...master)
1111

12+
## [v3.0.0](https://github.com/algolia/algoliasearch-client-python/compare/v2.6.2...v3.0.0)
13+
14+
### Changed
15+
16+
- Major version: Python 2.7 is no longer supported, adds support until Python 3.11
17+
18+
### Fixed
19+
- Unable to initialize SearchClient on Python 3.11 ([#549](https://github.com/algolia/algoliasearch-client-python/issues/549))
20+
1221
## [v2.6.3](https://github.com/algolia/algoliasearch-client-python/compare/v2.6.2...v2.6.3)
1322

1423
### Fixed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
## ✨ Features
2626

2727
- Thin & minimal low-level HTTP client to interact with Algolia's API
28-
- Supports Python: `2.7`, `3.4`, `3.5`, `3.6`, `3.7`, and `3.8`
28+
- Supports Python from `3.4` to `3.11`
2929
- Contains blazing-fast asynchronous methods built on top of the [Asyncio](https://docs.python.org/3/library/asyncio.html)
3030

3131
## 💡 Getting Started

algoliasearch/analytics_client_async.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import types
22

3-
import asyncio
43
from typing import Optional, Type
54

65
from algoliasearch.analytics_client import AnalyticsClient
@@ -23,22 +22,23 @@ def __init__(self, analytics_client, transporter, search_config):
2322

2423
_create_async_methods_in(self, client)
2524

26-
@asyncio.coroutine
27-
def __aenter__(self):
28-
# type: () -> AnalyticsClientAsync # type: ignore
25+
async def __aenter__(self):
26+
# type: () -> AnalyticsClientAsync
2927

30-
return self # type: ignore
28+
return self
3129

32-
@asyncio.coroutine
33-
def __aexit__(self, exc_type, exc, tb): # type: ignore
30+
async def __aexit__(self, exc_type, exc, tb): # type: ignore
3431
# type: (Optional[Type[BaseException]], Optional[BaseException],Optional[types.TracebackType]) -> None # noqa: E501
3532

36-
yield from self.close_async() # type: ignore
33+
self.close_async()
3734

38-
@asyncio.coroutine
39-
def close_async(self): # type: ignore
35+
return
36+
37+
async def close_async(self):
4038
# type: () -> None
4139

4240
super().close()
4341

44-
yield from self._transporter_async.close() # type: ignore
42+
self._transporter_async.close()
43+
44+
return

algoliasearch/helpers_async.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import asyncio
21
import types
32

43
from typing import Callable
@@ -29,7 +28,7 @@ def _gen_async(client, method):
2928

3029
m = getattr(client, method)
3130

32-
def closure(*args, **kwargs):
31+
async def closure(*args, **kwargs):
3332
result = m(*args, **kwargs)
3433

3534
# We make sure we resolve the promise from the raw_responses
@@ -38,16 +37,16 @@ def closure(*args, **kwargs):
3837
i,
3938
raw_response,
4039
) in enumerate(result.raw_responses):
41-
result.raw_responses[i] = yield from raw_response
40+
result.raw_responses[i] = await raw_response
4241

4342
# We make sure we resolve the promise from the raw_response
4443
if hasattr(result, "raw_response"):
45-
result.raw_response = yield from result.raw_response
44+
result.raw_response = await result.raw_response
4645

4746
# We make sure we resolve the promise
4847
if isinstance(result, types.GeneratorType):
49-
result = yield from result
48+
result = await result
5049

5150
return result
5251

53-
return asyncio.coroutine(closure)
52+
return closure

algoliasearch/http/requester_async.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ def __init__(self):
1313

1414
self._session = None
1515

16-
@asyncio.coroutine # type: ignore
17-
def send(self, request): # type: ignore
16+
async def send(self, request): # type: ignore
1817
# type: (Request) -> Response
1918

2019
if self._session is None:
@@ -29,7 +28,7 @@ def send(self, request): # type: ignore
2928

3029
try:
3130
with async_timeout.timeout(request.timeout):
32-
response = yield from (
31+
response = await (
3332
self._session.request( # type: ignore
3433
method=request.verb,
3534
url=request.url,
@@ -39,20 +38,19 @@ def send(self, request): # type: ignore
3938
)
4039
)
4140

42-
json = yield from response.json()
41+
json = await response.json()
4342

4443
except asyncio.TimeoutError as e:
4544

4645
return Response(error_message=str(e), is_timed_out_error=True)
4746

4847
return Response(response.status, json, str(response.reason))
4948

50-
@asyncio.coroutine
51-
def close(self): # type: ignore
49+
async def close(self): # type: ignore
5250
# type: () -> None
5351

5452
if self._session is not None:
5553
session = self._session
5654
self._session = None
5755

58-
yield from session.close() # type: ignore
56+
await session.close() # type: ignore

algoliasearch/http/transporter_async.py

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import asyncio
2-
31
from algoliasearch.configs import Config
42
from algoliasearch.exceptions import RequestException, AlgoliaUnreachableHostException
53
from algoliasearch.http.hosts import HostsCollection
@@ -13,15 +11,14 @@
1311

1412

1513
class TransporterAsync(Transporter):
16-
@asyncio.coroutine
17-
def retry(self, hosts, request, relative_url): # type: ignore
14+
async def retry(self, hosts, request, relative_url): # type: ignore
1815
# type: (list, Request, str) -> dict
1916

2017
for host in self._retry_strategy.valid_hosts(hosts):
2118

2219
request.url = "https://{}/{}".format(host.url, relative_url)
2320

24-
response = yield from self._requester.send(request) # type: ignore
21+
response = await self._requester.send(request) # type: ignore
2522

2623
decision = self._retry_strategy.decide(host, response)
2724

@@ -37,8 +34,7 @@ def retry(self, hosts, request, relative_url): # type: ignore
3734

3835
raise AlgoliaUnreachableHostException("Unreachable hosts")
3936

40-
@asyncio.coroutine
41-
def close(self): # type: ignore
37+
async def close(self): # type: ignore
4238
# type: () -> None
4339

44-
yield from self._requester.close() # type: ignore
40+
await self._requester.close() # type: ignore

algoliasearch/insights_client_async.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,22 @@ def user(self, user_token):
2727

2828
return UserInsightsClientAsync(self, user_token)
2929

30-
@asyncio.coroutine
31-
def __aenter__(self):
30+
async def __aenter__(self):
3231
# type: () -> InsightsClientAsync # type: ignore
3332

3433
return self # type: ignore
3534

36-
@asyncio.coroutine
37-
def __aexit__(self, exc_type, exc, tb): # type: ignore
35+
async def __aexit__(self, exc_type, exc, tb): # type: ignore
3836
# type: (Optional[Type[BaseException]], Optional[BaseException],Optional[types.TracebackType]) -> None # noqa: E501
3937

40-
yield from self.close_async() # type: ignore
38+
await self.close_async() # type: ignore
4139

42-
@asyncio.coroutine
43-
def close_async(self): # type: ignore
40+
async def close_async(self): # type: ignore
4441
# type: () -> None
4542

4643
super().close()
4744

48-
yield from self._transporter_async.close() # type: ignore
45+
await self._transporter_async.close() # type: ignore
4946

5047

5148
class UserInsightsClientAsync(UserInsightsClient):

0 commit comments

Comments
 (0)