Skip to content

Commit 83d8671

Browse files
committed
Add backdated support for Python 3.7 so it's supported in jupyter.org and Google colab notebooks
1 parent 87f8f66 commit 83d8671

File tree

7 files changed

+42
-34
lines changed

7 files changed

+42
-34
lines changed

requirements.txt

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
requests >= 2.25.1
2-
dataclasses >= 0.6
3-
dataclasses-json >= 0.5.4
4-
marshmallow >= 3.12.2
5-
setuptools >= 57.1.0
1+
typing_extensions >= 3.7.4
2+
requests >= 2.23.0
3+
dataclasses >= 0.6
4+
dataclasses-json >= 0.5.4
5+
marshmallow >= 3.12.2
6+
setuptools >= 57.1.0

servicestack/clients.py

+15-14
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
import json
33
from dataclasses import dataclass, field
44
from enum import Enum
5-
from typing import Callable, Type, Union, get_origin, ForwardRef
5+
from typing_extensions import Type, get_origin
6+
from typing import Callable, Union, ForwardRef
67
from typing import TypeVar, Optional, Dict, List, Any
78
from urllib.parse import urljoin, quote_plus
89

@@ -91,7 +92,7 @@ def qsvalue(arg):
9192
return quote_plus(str(arg))
9293

9394

94-
def append_querystring(url: str, args: dict[str, Any]):
95+
def append_querystring(url: str, args: Dict[str, Any]):
9596
if args:
9697
for key in args:
9798
val = args[key]
@@ -113,13 +114,13 @@ def has_request_body(method: str):
113114
@dataclass
114115
class SendContext:
115116
session: Optional[requests.Session] = None
116-
headers: dict[str, str] = field(default_factory=dict)
117+
headers: Dict[str, str] = field(default_factory=dict)
117118
method: str = None
118119
url: Optional[str] = None
119120
request: Optional[Union[IReturn, IReturnVoid, List[IReturn], List[IReturnVoid]]] = None
120121
body: Optional[Any] = None
121122
body_string: Optional[str] = None
122-
args: Optional[dict[str, str]] = None
123+
args: Optional[Dict[str, str]] = None
123124
response_as: type = None
124125
request_filter: Callable[[Any], None] = None
125126
response_filter: Callable[[Response], None] = None
@@ -270,29 +271,29 @@ def to_absolute_url(self, path_or_url: str):
270271
return path_or_url
271272
return urljoin(self.base_url, path_or_url)
272273

273-
def get_url(self, path: str, response_as: Type, args: dict[str, Any] = None):
274+
def get_url(self, path: str, response_as: Type, args: Dict[str, Any] = None):
274275
return self.send_url(path, "GET", response_as, None, args)
275276

276-
def delete_url(self, path: str, response_as: Type, args: dict[str, Any] = None):
277+
def delete_url(self, path: str, response_as: Type, args: Dict[str, Any] = None):
277278
return self.send_url(path, "DELETE", response_as, None, args)
278279

279-
def options_url(self, path: str, response_as: Type, args: dict[str, Any] = None):
280+
def options_url(self, path: str, response_as: Type, args: Dict[str, Any] = None):
280281
return self.send_url(path, "OPTIONS", response_as, None, args)
281282

282-
def head_url(self, path: str, response_as: Type, args: dict[str, Any] = None):
283+
def head_url(self, path: str, response_as: Type, args: Dict[str, Any] = None):
283284
return self.send_url(path, "HEAD", response_as, None, args)
284285

285-
def post_url(self, path: str, body: Any = None, response_as: Type = None, args: dict[str, Any] = None):
286+
def post_url(self, path: str, body: Any = None, response_as: Type = None, args: Dict[str, Any] = None):
286287
return self.send_url(path, "POST", response_as, body, args)
287288

288-
def put_url(self, path: str, body: Any = None, response_as: Type = None, args: dict[str, Any] = None):
289+
def put_url(self, path: str, body: Any = None, response_as: Type = None, args: Dict[str, Any] = None):
289290
return self.send_url(path, "PUT", response_as, body, args)
290291

291-
def patch_url(self, path: str, body: Any = None, response_as: Type = None, args: dict[str, Any] = None):
292+
def patch_url(self, path: str, body: Any = None, response_as: Type = None, args: Dict[str, Any] = None):
292293
return self.send_url(path, "PATCH", response_as, body, args)
293294

294295
def send_url(self, path: str, method: str = None, response_as: Type = None, body: Any = None,
295-
args: dict[str, Any] = None):
296+
args: Dict[str, Any] = None):
296297

297298
if body and not response_as:
298299
response_as = _resolve_response_type(body)
@@ -359,7 +360,7 @@ def send_all(self, request_dtos: List[IReturn[T]]):
359360
body=None,
360361
body_string=None,
361362
args=None,
362-
response_as=list.__class_getitem__(item_response_as)))
363+
response_as=list.__class_getitem__(item_response_as))) # requires 3.9
363364

364365
def send_all_oneway(self, request_dtos: list):
365366
request, item_response_as = self.assert_valid_batch_request(request_dtos)
@@ -374,7 +375,7 @@ def send_all_oneway(self, request_dtos: list):
374375
body=None,
375376
body_string=None,
376377
args=None,
377-
response_as=list.__class_getitem__(item_response_as)))
378+
response_as=list.__class_getitem__(item_response_as))) # requires 3.9
378379

379380
def _resend_request(self, info: SendContext):
380381

servicestack/log.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from abc import ABC, abstractmethod
22
from enum import Enum
3+
from typing import List
34

45

56
class LogLevel(str, Enum):
@@ -28,7 +29,7 @@ def log(self, level: LogLevel, msg: str, e: Exception = None):
2829

2930

3031
class Log:
31-
levels: list[LogLevel] = [LogLevel.WARN, LogLevel.ERROR]
32+
levels: List[LogLevel] = [LogLevel.WARN, LogLevel.ERROR]
3233
logger: Logger = ConsoleLogger()
3334

3435
@staticmethod

servicestack/reflection.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from enum import Enum, IntEnum, EnumMeta
1515
from functools import reduce
1616
from types import MappingProxyType
17-
from typing import Callable, Type, get_origin, ForwardRef, Union, TypeVar, get_args
17+
from typing_extensions import Type, get_origin, get_args
18+
from typing import Callable, ForwardRef, Union, TypeVar
1819
from typing import List, Dict, Any
1920

2021
import marshmallow.fields as mf
@@ -175,8 +176,8 @@ def to_json(obj: Any, indent=None):
175176

176177

177178
class TypeConverters:
178-
serializers: dict[Type, Callable[[Any], Any]]
179-
deserializers: dict[Type, Callable[[Any], Any]]
179+
serializers = {} # Py3.9: dict[Type, Callable[[Any], Any]]
180+
deserializers = {} # Py3.9: dict[Type, Callable[[Any], Any]]
180181

181182
@staticmethod
182183
def register(cls: Type, serializer: Callable[[Any], Any] = None, deserializer: Callable[[Any], Any] = None):

setup.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@
55
with open("README.md", "r", encoding="utf-8") as fh:
66
long_description = fh.read()
77

8-
dependencies = ['requests>=2.25.1', 'dataclasses>=0.6', 'dataclasses-json>=0.5.4', 'marshmallow>=3.12.2']
8+
dependencies = ['typing_extensions>=3.7.4', 'requests>=2.23.0', 'dataclasses>=0.6', 'dataclasses-json>=0.5.4',
9+
'marshmallow>=3.12.2']
910

1011
setuptools.setup(
1112
name="servicestack",
12-
version="0.0.8",
13+
version="0.1.1",
1314
author="ServiceStack, Inc.",
1415
author_email="[email protected]",
1516
description="ServiceStack Python Service Clients",
@@ -20,15 +21,17 @@
2021
keywords='servicestack,client,dotnet',
2122
install_requires=dependencies,
2223
packages=setuptools.find_packages(),
23-
tests_require=['pytest', 'dataclasses>=0.6', 'dataclasses-json>=0.5.4', 'requests>=2.25.1'],
24+
tests_require=['pytest', 'dataclasses>=0.6', 'dataclasses-json>=0.5.4', 'requests>=2.23.0'],
2425
classifiers=[
2526
'Intended Audience :: Developers',
2627
'License :: OSI Approved :: BSD License',
2728
'Natural Language :: English',
2829
"Operating System :: OS Independent",
30+
'Programming Language :: Python :: 3.7',
31+
'Programming Language :: Python :: 3.8',
2932
'Programming Language :: Python :: 3.9',
3033
'Topic :: Software Development :: Libraries :: Python Modules'
3134
],
3235
py_modules=["servicestack"],
33-
python_requires='>=3.9',
36+
python_requires='>=3.7',
3437
)

tests/test_client.py

+3
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,7 @@ def test_can_deserialize_custom_generic_response_type(self):
337337
all_names = list(map(lambda x: x.name, response.results))
338338
self.assertLessEqual(all_names, ["item 1", "item 2"])
339339

340+
# requires Python 3.9
340341
def test_can_send_all_batch_request(self):
341342
client = create_test_client()
342343
client.response_filter = lambda res: self.assertEqual(res.headers["X-AutoBatch-Completed"], "3")
@@ -345,12 +346,14 @@ def test_can_send_all_batch_request(self):
345346
self.assertListEqual(list(map(lambda x: x.result, responses)),
346347
['Hello, foo!', 'Hello, bar!', 'Hello, baz!'])
347348

349+
# requires Python 3.9
348350
def test_can_send_all_oneway_IReturn_batch_request(self):
349351
client = create_test_client()
350352
client.request_filter = lambda req: self.assertTrue(req.url.endswith("/json/oneway/Hello[]"))
351353
requests = list(map(lambda name: Hello(name=name), ["foo", "bar", "baz"]))
352354
client.send_all_oneway(requests)
353355

356+
# requires Python 3.9
354357
def test_can_send_all_oneway_IReturnVoid_batch_request(self):
355358
client = create_test_client()
356359
client.request_filter = lambda req: self.assertTrue(req.url.endswith("/json/oneway/HelloReturnVoid[]"))

tests/test_client_auth.py

+5-7
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,12 @@ def test_can_use_on_authentication_required_to_auth_client(self):
6868
def test_can_use_on_authentication_required_to_fetch_token(self):
6969
client = create_test_client()
7070
state = State()
71+
auth_client = create_test_client()
7172

72-
client.on_authentication_required = lambda c=client, s=state: [
73+
client.on_authentication_required = lambda c=client, a=auth_client, s=state: [
7374
s.incr(),
74-
auth_client := create_test_client(),
75-
auth_client.set_credentials("test", "test"),
76-
response := cast(AuthenticateResponse, auth_client.get(Authenticate())),
77-
client.set_bearer_token(response.bearer_token)
75+
a.set_credentials("test", "test"),
76+
client.set_bearer_token(cast(AuthenticateResponse, a.get(Authenticate())).bearer_token)
7877
]
7978

8079
client.get(TestAuth())
@@ -86,8 +85,7 @@ def test_can_use_on_authentication_required_to_fetch_token_after_expired_token(s
8685

8786
client.on_authentication_required = lambda c=client, s=state: [
8887
s.incr(),
89-
fresh_jwt := cast(CreateJwtResponse, c.post(create_jwt())),
90-
c.set_bearer_token(fresh_jwt.token)
88+
c.set_bearer_token(cast(CreateJwtResponse, c.post(create_jwt())).token)
9189
]
9290

9391
create_expired_jwt = create_jwt()

0 commit comments

Comments
 (0)