Skip to content

Commit 3b07463

Browse files
committed
replace wildcard imports to explicit to avoid importing imports
1 parent f689630 commit 3b07463

File tree

9 files changed

+205
-78
lines changed

9 files changed

+205
-78
lines changed

requirements.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
requests >= 2.25.1
22
dataclasses >= 0.6
33
dataclasses-json >= 0.5.4
4+
marshmallow >= 3.12.2
5+
setuptools >= 57.1.0
6+
stringcase >= 1.2.0

servicestack/__init__.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,30 @@
9191
'Bytes',
9292
]
9393

94-
from .dtos import *
95-
from .clients import TypeConverters, JsonServiceClient, WebServiceException, WebServiceExceptionType, to_json, \
96-
from_json, convert, qsvalue, resolve_httpmethod
97-
from .utils import *
98-
from .fields import *
94+
from .dtos import \
95+
IReturn, IReturnVoid, IGet, IPost, IPut, IDelete, IPatch, IOptions, IMeta, \
96+
IHasSessionId, IHasBearerToken, IHasVersion, ICrud, \
97+
ICreateDb, IUpdateDb, IPatchDb, IDeleteDb, ISaveDb, \
98+
KeyValuePair, ResponseError, ResponseStatus, QueryBase, \
99+
QueryDb, QueryDb1, QueryDb2, QueryData, QueryData2, QueryResponse, \
100+
AuthenticateResponse, Authenticate, AssignRolesResponse, AssignRoles, \
101+
UnAssignRolesResponse, UnAssignRoles, ConvertSessionToTokenResponse, \
102+
ConvertSessionToToken, GetAccessTokenResponse, GetAccessToken, \
103+
CancelRequestResponse, CancelRequest, UpdateEventSubscriber, \
104+
UpdateEventSubscriberResponse, UserApiKey, GetApiKeys, GetApiKeysResponse, \
105+
RegenerateApiKeys, RegenerateApiKeysResponse, NavItem, GetNavItems, \
106+
GetNavItemsResponse, EmptyResponse, IdResponse, StringResponse, \
107+
StringsResponse, AuditBase
108+
109+
from .clients import TypeConverters, JsonServiceClient, WebServiceException, \
110+
WebServiceExceptionType, to_json, from_json, convert, qsvalue, \
111+
resolve_httpmethod
112+
113+
from .utils import \
114+
is_optional, is_list, is_dict, generic_args, generic_arg, index_of, \
115+
last_index_of, left_part, right_part, last_left_part, last_right_part, \
116+
split_on_first, split_on_last, to_timespan, from_timespan, from_datetime, \
117+
to_bytearray, from_bytearray, from_base64url_safe, inspect_jwt, \
118+
inspect_vars, dump, printdump, dumptable, printdumptable
119+
120+
from .fields import Bytes

servicestack/clients.py

Lines changed: 71 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,28 @@
1+
import base64
2+
import datetime
13
import decimal
24
import inspect
3-
import typing
4-
from dataclasses import field, fields, asdict, is_dataclass
5+
import json
6+
from dataclasses_json import dataclass_json
7+
from dataclasses import dataclass, field, fields, asdict, is_dataclass
8+
from datetime import datetime, timedelta
59
from enum import Enum, IntEnum
610
from typing import Callable, get_args, Type, get_origin, ForwardRef, Union
11+
from typing import TypeVar, Optional, Dict, List, Any
712
from urllib.parse import urljoin, quote_plus
813

14+
import marshmallow.fields as mf
915
import requests
1016
from requests.exceptions import HTTPError
1117
from requests.models import Response
1218
from stringcase import camelcase, snakecase, uppercase
1319

14-
from servicestack.dtos import *
15-
from servicestack.fields import *
20+
from servicestack.dtos import IReturn, IReturnVoid, IGet, IPost, IPut, IPatch, \
21+
IDelete, ResponseStatus, EmptyResponse, GetAccessToken, GetAccessTokenResponse
1622
from servicestack.log import Log
23+
from servicestack.utils import is_list, is_dict, to_timespan, to_datetime, \
24+
to_bytearray, from_bytearray, from_datetime, from_timespan, is_optional, \
25+
generic_args, generic_arg
1726

1827
JSON_MIME_TYPE = "application/json"
1928
AUTHORIZATION_HEADER = "Authorization"
@@ -120,8 +129,8 @@ def qsvalue(arg):
120129
return "{" + ','.join([k + ":" + qsvalue(v) for k, v in arg]) + "}"
121130
if arg_type is str:
122131
return quote_plus(arg)
123-
if arg_type is bytes or arg_type is bytearray:
124-
return base64.b64encode(arg).decode("utf-8")
132+
if arg_type in TypeConverters.serializers:
133+
return TypeConverters.serialize(arg)
125134
return quote_plus(str(arg))
126135

127136

@@ -132,7 +141,9 @@ def append_querystring(url: str, args: dict[str, Any]):
132141
if val is None:
133142
continue
134143
url += '&' if '?' in url else '?'
135-
url += key + '=' + qsvalue(val)
144+
qs_val = qsvalue(val)
145+
if qs_val is not None:
146+
url += key + '=' + qs_val
136147
return url
137148

138149

@@ -144,6 +155,19 @@ def _empty(x):
144155
return x is None or x == {} or x == []
145156

146157

158+
def _asdict(obj: Any):
159+
if obj is None:
160+
return {}
161+
if is_dataclass(obj):
162+
d = asdict(obj)
163+
to = {}
164+
for k, v in d.items():
165+
to[camelcase(k)] = v
166+
else:
167+
to = obj.__dict__
168+
return clean_any(to)
169+
170+
147171
def _clean_list(d: list):
148172
return [v for v in (clean_any(v) for v in d) if not _empty(v)]
149173

@@ -164,7 +188,7 @@ def clean_any(d):
164188

165189
def _json_encoder(obj: Any):
166190
if is_dataclass(obj):
167-
return clean_any(asdict(obj))
191+
return _asdict(obj)
168192
if hasattr(obj, '__dict__'):
169193
return vars(obj)
170194
if isinstance(obj, datetime):
@@ -187,16 +211,50 @@ def to_json(obj: Any):
187211

188212

189213
class TypeConverters:
214+
serializers: dict[Type, Callable[[Any], Any]]
190215
deserializers: dict[Type, Callable[[Any], Any]]
191216

192217
@staticmethod
193-
def register_deserializer(cls: Type, deserializer: Callable[[Any], Any]):
194-
TypeConverters.deserializers[cls] = deserializer
218+
def register(cls: Type, serializer: Callable[[Any], Any] = None, deserializer: Callable[[Any], Any] = None):
219+
if serializer is not None:
220+
TypeConverters.serializers[cls] = serializer
221+
if deserializer is not None:
222+
TypeConverters.deserializers[cls] = deserializer
195223

224+
@staticmethod
225+
def serialize(obj: Any):
226+
cls = type(obj)
227+
if cls in TypeConverters.serializers:
228+
serializer = TypeConverters.serializers[cls]
229+
try:
230+
return serializer(obj)
231+
except Exception as e:
232+
Log.error(f"serializer(obj) {cls}({obj})", e)
233+
raise e
234+
235+
@staticmethod
236+
def deserialize(cls: Type, obj: Any):
237+
deserializer = TypeConverters.deserializers[cls]
238+
try:
239+
return deserializer(obj)
240+
except Exception as e:
241+
Log.error(f"deserializer(obj) {cls}({obj})", e)
242+
raise e
196243

244+
245+
TypeConverters.serializers = {
246+
datetime: to_datetime,
247+
timedelta: to_timespan,
248+
bytes: to_bytearray,
249+
bytearray: to_bytearray,
250+
}
197251
TypeConverters.deserializers = {
198252
mf.DateTime: from_datetime,
199253
mf.TimeDelta: from_timespan,
254+
datetime: from_datetime,
255+
timedelta: from_timespan,
256+
bytes: from_bytearray,
257+
bytearray: from_bytearray,
200258
}
201259

202260

@@ -239,7 +297,7 @@ def sanitize_name(s: str):
239297
return s.replace('_', '').upper()
240298

241299

242-
def enum_get(cls: Enum, key: Union[str, int]):
300+
def enum_get(cls: Union[Enum, Type], key: Union[str, int]):
243301
if type(key) == int or issubclass(cls, IntEnum):
244302
return cls(key)
245303
try:
@@ -490,7 +548,7 @@ def refresh_token_cookie(self):
490548
def create_url_from_dto(self, method: str, request: Any):
491549
url = urljoin(self.reply_base_url, nameof(request))
492550
if not has_request_body(method):
493-
url = append_querystring(url, request.__dict__)
551+
url = append_querystring(url, _asdict(request))
494552
return url
495553

496554
def get(self, request: IReturn[T], args: Dict[str, Any] = None) -> T:
@@ -723,7 +781,7 @@ def create_request(self, info: SendContext):
723781
body_not_request_dto = info.request and info.body
724782
if body_not_request_dto:
725783
url = urljoin(self.reply_base_url, nameof(info.request))
726-
url = append_querystring(url, clean_any(asdict(info.request)))
784+
url = append_querystring(url, _asdict(info.request))
727785
else:
728786
url = self.create_url_from_dto(info.method, body)
729787

servicestack/fields.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from servicestack.utils import *
1+
from servicestack.utils import to_bytearray, from_bytearray
22
import marshmallow.fields as mf
33

44

servicestack/utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from typing import Optional, List, Dict, Any, Type, get_origin, Union, get_args
1111
from dataclasses import dataclass, field, asdict
1212
from functools import reduce
13+
from .log import Log
1314

1415

1516
def is_optional(cls: Type): return get_origin(cls) is Union and type(None) in get_args(cls)
@@ -172,6 +173,14 @@ def from_timespan(s: Optional[str]):
172173
_MAX_UTC_DATE = datetime.max.replace(tzinfo=timezone.utc)
173174

174175

176+
def to_datetime(date: datetime):
177+
try:
178+
return f"/Date({int(date.timestamp() * 1000)})/"
179+
except Exception as e:
180+
Log.debug(f"to_datetime({date}): e")
181+
return None
182+
183+
175184
def from_datetime(json_date: str):
176185
if json_date.startswith("/Date("):
177186
epoch_and_zone = left_part(right_part(json_date, "("), ")")

0 commit comments

Comments
 (0)