kisa-utils 0.36.2__py3-none-any.whl → 0.36.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kisa_utils/functionUtils.py +18 -10
- kisa_utils/servers/flask.py +65 -7
- kisa_utils/structures/utils.py +15 -4
- kisa_utils/structures/validator.py +11 -6
- {kisa_utils-0.36.2.dist-info → kisa_utils-0.36.4.dist-info}/METADATA +1 -1
- {kisa_utils-0.36.2.dist-info → kisa_utils-0.36.4.dist-info}/RECORD +8 -8
- {kisa_utils-0.36.2.dist-info → kisa_utils-0.36.4.dist-info}/WHEEL +0 -0
- {kisa_utils-0.36.2.dist-info → kisa_utils-0.36.4.dist-info}/top_level.txt +0 -0
kisa_utils/functionUtils.py
CHANGED
|
@@ -6,7 +6,7 @@ from kisa_utils.response import Response, Ok, Error
|
|
|
6
6
|
from kisa_utils.storage import Path
|
|
7
7
|
from kisa_utils.structures.validator import Value, validateWithResponse
|
|
8
8
|
import copy
|
|
9
|
-
from typing import Any
|
|
9
|
+
from typing import Any, get_args
|
|
10
10
|
|
|
11
11
|
class Definition:
|
|
12
12
|
@staticmethod
|
|
@@ -63,7 +63,7 @@ def enforceRequirements(func):
|
|
|
63
63
|
typeHints = func.__annotations__
|
|
64
64
|
parameters = signature.parameters
|
|
65
65
|
|
|
66
|
-
if not func.__doc__:
|
|
66
|
+
if not func.__doc__.strip():
|
|
67
67
|
raise ValueError(
|
|
68
68
|
f"function `{func.__name__}` has no docString!")
|
|
69
69
|
|
|
@@ -83,7 +83,8 @@ def enforceRequirements(func):
|
|
|
83
83
|
if value.default is not inspect.Parameter.empty:
|
|
84
84
|
if not (resp := validateWithResponse(value.default, typeHints[key])):
|
|
85
85
|
raise ValueError(f'arg `{key}` default value: {resp.log}')
|
|
86
|
-
|
|
86
|
+
|
|
87
|
+
registeredArgs.append((key, typeHints[key] ))
|
|
87
88
|
elif value.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
88
89
|
if value.default is not inspect.Parameter.empty:
|
|
89
90
|
if not (resp := validateWithResponse(value.default, typeHints[key])):
|
|
@@ -100,6 +101,8 @@ def enforceRequirements(func):
|
|
|
100
101
|
|
|
101
102
|
expectectedReturnType = typeHints['return']
|
|
102
103
|
|
|
104
|
+
# print(registeredArgs, registeredKwargs)
|
|
105
|
+
|
|
103
106
|
@wraps(func)
|
|
104
107
|
def w(*args, **kwargs):
|
|
105
108
|
for index, arg in enumerate(args):
|
|
@@ -120,14 +123,19 @@ def enforceRequirements(func):
|
|
|
120
123
|
|
|
121
124
|
resp = func(*args, **kwargs)
|
|
122
125
|
|
|
123
|
-
if not isinstance(resp, expectectedReturnType):
|
|
124
|
-
|
|
125
|
-
log = f'`{func.__name__}` returned `{resp.__name__}`, expected `{expectectedReturnType}`'
|
|
126
|
-
else:
|
|
127
|
-
log = f'`{func.__name__}` returned `{type(resp).__name__}`, expected `{expectectedReturnType}`'
|
|
128
|
-
|
|
126
|
+
# if not isinstance(resp, expectectedReturnType):
|
|
127
|
+
if not (resp := validateWithResponse(resp, expectectedReturnType)):
|
|
129
128
|
if Response == expectectedReturnType: return Error(log)
|
|
130
|
-
|
|
129
|
+
|
|
130
|
+
raise TypeError(f'`{func.__name__}` return error: {resp.log}')
|
|
131
|
+
|
|
132
|
+
# if isinstance(resp, type):
|
|
133
|
+
# log = f'`{func.__name__}` returned `{resp.__name__}`, expected `{expectectedReturnType}`'
|
|
134
|
+
# else:
|
|
135
|
+
# log = f'`{func.__name__}` returned `{type(resp).__name__}`, expected `{expectectedReturnType}`'
|
|
136
|
+
|
|
137
|
+
# if Response == expectectedReturnType: return Error(log)
|
|
138
|
+
# raise TypeError(log)
|
|
131
139
|
|
|
132
140
|
return resp
|
|
133
141
|
|
kisa_utils/servers/flask.py
CHANGED
|
@@ -5,7 +5,7 @@ Kisa Server Utilities
|
|
|
5
5
|
import kisa_utils as kutils
|
|
6
6
|
from kisa_utils.storage import Path
|
|
7
7
|
from kisa_utils.response import Response, Ok, Error
|
|
8
|
-
from flask import Flask, request, jsonify, wrappers, render_template_string
|
|
8
|
+
from flask import Flask, request, jsonify, wrappers, render_template_string, current_app, g as app_ctx
|
|
9
9
|
from flask_cors import CORS
|
|
10
10
|
from functools import wraps
|
|
11
11
|
import copy
|
|
@@ -25,6 +25,7 @@ import types
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
_VALID_HTTP_METHODS = {'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD', 'OPTIONS'}
|
|
28
|
+
_WHITE_LISTED_PARAMS = ["headers"]
|
|
28
29
|
|
|
29
30
|
from typing import Optional, Callable
|
|
30
31
|
from functools import wraps
|
|
@@ -42,6 +43,27 @@ def __init():
|
|
|
42
43
|
globals()['__SERVER_APP'] = __app
|
|
43
44
|
globals()['__BASIC_AUTH'] = __basic_auth
|
|
44
45
|
|
|
46
|
+
# change logger to include time spend on a request
|
|
47
|
+
import logging, logging.handlers, time
|
|
48
|
+
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
|
49
|
+
logging.basicConfig(level=logging.INFO)
|
|
50
|
+
__app.logger.addHandler(logging.handlers.SysLogHandler(address="/dev/log")) # Address for journald
|
|
51
|
+
|
|
52
|
+
@__app.before_request
|
|
53
|
+
def beforeRequest():
|
|
54
|
+
app_ctx.start_time = time.perf_counter()
|
|
55
|
+
app_ctx.start_timestamp = kutils.dates.currentTimestamp()
|
|
56
|
+
|
|
57
|
+
@__app.after_request
|
|
58
|
+
def logging_after(response):
|
|
59
|
+
# Get total time in milliseconds
|
|
60
|
+
total_time = round(time.perf_counter() - app_ctx.start_time, 4)
|
|
61
|
+
# Log the time taken for the endpoint
|
|
62
|
+
|
|
63
|
+
current_app.logger.info('[%s %s %s %d] %s', request.method, app_ctx.start_timestamp, total_time, response.status_code, request.path)
|
|
64
|
+
|
|
65
|
+
return response
|
|
66
|
+
|
|
45
67
|
|
|
46
68
|
__init()
|
|
47
69
|
|
|
@@ -238,11 +260,13 @@ def endpoint(
|
|
|
238
260
|
|
|
239
261
|
signature = inspect.signature(func)
|
|
240
262
|
typeHints = func.__annotations__
|
|
241
|
-
parameters = signature.parameters
|
|
263
|
+
# parameters = signature.parameters
|
|
242
264
|
|
|
243
|
-
|
|
265
|
+
headers_type = typeHints.get("headers")
|
|
266
|
+
if headers_type and not (headers_type is dict):
|
|
267
|
+
raise TypeError("arg::headers type must be 'dict'")
|
|
244
268
|
|
|
245
|
-
if not handler.__doc__:
|
|
269
|
+
if not handler.__doc__ and not (handler.__doc__.strip()):
|
|
246
270
|
raise ValueError(
|
|
247
271
|
f"handler function {func.__name__} has no docString!")
|
|
248
272
|
|
|
@@ -265,6 +289,9 @@ def endpoint(
|
|
|
265
289
|
f"function `{handler.__name__}` should take either positional-only or keyword-only parameters")
|
|
266
290
|
|
|
267
291
|
if value.kind == inspect.Parameter.POSITIONAL_ONLY:
|
|
292
|
+
if key == "headers":
|
|
293
|
+
raise ValueError(f"arg: {key}, should ony be passed as keyword_only argument!")
|
|
294
|
+
|
|
268
295
|
if value.default is not inspect.Parameter.empty:
|
|
269
296
|
if not (resp := validateWithResponse(value.default, typeHints[key])):
|
|
270
297
|
raise ValueError(f'arg `{key}` default value: {resp.log}')
|
|
@@ -273,8 +300,12 @@ def endpoint(
|
|
|
273
300
|
args.append((key,))
|
|
274
301
|
elif value.kind == inspect.Parameter.KEYWORD_ONLY:
|
|
275
302
|
if value.default is not inspect.Parameter.empty:
|
|
303
|
+
if key == "headers":
|
|
304
|
+
raise ValueError(f'arg `{key}` should have no default value')
|
|
305
|
+
|
|
276
306
|
if not (resp := validateWithResponse(value.default, typeHints[key])):
|
|
277
307
|
raise ValueError(f'arg `{key}` default value: {resp.log}')
|
|
308
|
+
|
|
278
309
|
kwargs.append((key, value.default))
|
|
279
310
|
else:
|
|
280
311
|
kwargs.append((key,))
|
|
@@ -287,6 +318,10 @@ def endpoint(
|
|
|
287
318
|
if not hint:
|
|
288
319
|
raise TypeError(f"parameter {key} has no type hint")
|
|
289
320
|
|
|
321
|
+
# print(typeHints)
|
|
322
|
+
if "headers" in typeHints and "headers" in _WHITE_LISTED_PARAMS:
|
|
323
|
+
del typeHints["headers"]
|
|
324
|
+
|
|
290
325
|
validationStructure = copy.deepcopy(typeHints)
|
|
291
326
|
del validationStructure["return"]
|
|
292
327
|
|
|
@@ -308,7 +343,6 @@ def endpoint(
|
|
|
308
343
|
return resp
|
|
309
344
|
|
|
310
345
|
payload = resp.data
|
|
311
|
-
# print("pyload====>", payload)
|
|
312
346
|
|
|
313
347
|
# Handle text payloads differently
|
|
314
348
|
if isinstance(payload, (str, bytes)):
|
|
@@ -317,7 +351,6 @@ def endpoint(
|
|
|
317
351
|
raise BadRequest("Text input requires exactly one positional parameter")
|
|
318
352
|
|
|
319
353
|
nonlocal validationStructure
|
|
320
|
-
|
|
321
354
|
_args = []
|
|
322
355
|
_kwargs = {}
|
|
323
356
|
|
|
@@ -334,10 +367,15 @@ def endpoint(
|
|
|
334
367
|
|
|
335
368
|
for item in kwargs:
|
|
336
369
|
kwarg = item[0]
|
|
370
|
+
_headers_present = True if kwarg == "headers" else False
|
|
337
371
|
|
|
338
372
|
if kwarg in payload:
|
|
339
373
|
_kwargs[kwarg] = payload[kwarg]
|
|
340
374
|
else:
|
|
375
|
+
if _headers_present and kwarg == "headers":
|
|
376
|
+
_kwargs[kwarg] = dict(request.headers)
|
|
377
|
+
continue
|
|
378
|
+
|
|
341
379
|
if len(item) == 2:
|
|
342
380
|
default = item[1]
|
|
343
381
|
_kwargs[kwarg] = default
|
|
@@ -785,8 +823,28 @@ def _convert_single_type(value:Any, target_type:type)->Any:
|
|
|
785
823
|
except (ValueError, TypeError) as e:
|
|
786
824
|
raise ValueError(f"Cannot convert {value} to {target_type.__name__}: {str(e)}")
|
|
787
825
|
|
|
826
|
+
@enforceRequirements
|
|
827
|
+
def parse_headers(headers_str: str, /) -> dict:
|
|
828
|
+
"""Parses a raw HTTP headers string into a dictionary.
|
|
829
|
+
|
|
830
|
+
Args:
|
|
831
|
+
headers_str: Raw headers string (
|
|
832
|
+
e.g., "Content-Type: application/json\nAuthorization: Bearer token"
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
Returns:
|
|
836
|
+
Dictionary with header names as keys and header values as values.
|
|
837
|
+
"""
|
|
838
|
+
headers = {}
|
|
839
|
+
|
|
840
|
+
for line in headers_str.strip().splitlines():
|
|
841
|
+
if ':' in line:
|
|
842
|
+
key, value = line.split(':', 1)
|
|
843
|
+
headers[key.strip()] = value.strip()
|
|
844
|
+
|
|
845
|
+
return headers
|
|
788
846
|
|
|
789
847
|
# startServer()
|
|
790
848
|
if __name__ == "__main__":
|
|
791
849
|
# print('======> app:', getAppInstance())
|
|
792
|
-
startServer()
|
|
850
|
+
startServer()
|
kisa_utils/structures/utils.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import typing
|
|
3
|
-
from typing import Any, Callable
|
|
3
|
+
from typing import Any, Callable, get_origin
|
|
4
4
|
from types import UnionType
|
|
5
5
|
from kisa_utils.response import Response
|
|
6
6
|
|
|
@@ -30,13 +30,23 @@ class Value:
|
|
|
30
30
|
raise TypeError(f'validator should take only 1 argument (not counting `self` for methods)')
|
|
31
31
|
|
|
32
32
|
reply = validator(
|
|
33
|
-
valueType() if type(valueType)==type(type)
|
|
33
|
+
valueType() if type(valueType)==type(type) \
|
|
34
|
+
else (
|
|
35
|
+
(
|
|
36
|
+
_:=list(typing.get_args(valueType))[0]
|
|
37
|
+
)
|
|
38
|
+
and (
|
|
39
|
+
get_origin(_) or _
|
|
40
|
+
)
|
|
41
|
+
)()
|
|
34
42
|
)
|
|
35
43
|
|
|
36
44
|
if not isinstance(reply, Response):
|
|
37
45
|
raise TypeError(f'`validator` must return kutils.response.Response object')
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
typesTuple = valueType
|
|
48
|
+
|
|
49
|
+
self._valueType = typesTuple
|
|
40
50
|
self._validator = validator
|
|
41
51
|
|
|
42
52
|
def validate(self, valueInstance:Any, /) -> Response:
|
|
@@ -47,8 +57,9 @@ class Value:
|
|
|
47
57
|
|
|
48
58
|
@property
|
|
49
59
|
def __name__(self) -> str:
|
|
60
|
+
print(self._valueType)
|
|
50
61
|
if not isinstance(self._valueType, UnionType):
|
|
51
|
-
_type = self._valueType.__name__
|
|
62
|
+
_type = (get_origin(self._valueType) or self._valueType).__name__
|
|
52
63
|
else:
|
|
53
64
|
types = self._valueType.__args__
|
|
54
65
|
_type = '|'.join(_.__name__ for _ in types)
|
|
@@ -3,7 +3,7 @@ this modules handle data structure validation to ensure that
|
|
|
3
3
|
data is passed in expected formats/structures
|
|
4
4
|
'''
|
|
5
5
|
|
|
6
|
-
from typing import Any, get_args
|
|
6
|
+
from typing import Any, get_args, get_origin
|
|
7
7
|
from types import UnionType
|
|
8
8
|
from kisa_utils.structures.utils import Value
|
|
9
9
|
from kisa_utils.response import Response, Ok, Error
|
|
@@ -20,18 +20,23 @@ def validate(instance:Any, structure:Any, path:str='$') -> dict:
|
|
|
20
20
|
|
|
21
21
|
result = {'status':False, 'log':''}
|
|
22
22
|
|
|
23
|
+
print(instance, structure, end=' => ')
|
|
24
|
+
if not isinstance(structure, UnionType) and get_origin(structure):
|
|
25
|
+
structure = get_origin(structure)
|
|
26
|
+
print(instance, structure)
|
|
27
|
+
|
|
23
28
|
# union types such as int|float...
|
|
24
29
|
if isinstance(structure, UnionType):
|
|
25
|
-
if not isinstance(instance, get_args(structure)):
|
|
26
|
-
expectedTypes = [str(_).split("'")[1] for _ in get_args(structure)]
|
|
30
|
+
if not isinstance(instance, tuple([get_origin(t) or t for t in get_args(structure)])):
|
|
31
|
+
expectedTypes = [str(_).split("'")[1] for _ in tuple([(get_args(t) or t) for t in get_args(structure)])]
|
|
27
32
|
result['log'] = f'E06: types not similar:: {path}, expected one of {"|".join(expectedTypes)} but got {str(type(instance))[7:-1]}'
|
|
28
33
|
return result
|
|
29
34
|
|
|
30
35
|
# when the structure is a block type/class eg dict,list,tuple,etc
|
|
31
|
-
elif type(structure)==type(type) or isinstance(structure, Value):
|
|
32
|
-
_structure = structure._valueType if isinstance(structure, Value) else structure
|
|
36
|
+
elif type(structure)==type(type) or isinstance(structure, Value) or get_args(type):
|
|
37
|
+
_structure = structure._valueType if isinstance(structure, Value) else (get_args(structure) or structure)
|
|
33
38
|
|
|
34
|
-
if instance!=_structure and not isinstance(instance, _structure):
|
|
39
|
+
if instance != _structure and not isinstance(instance, _structure):
|
|
35
40
|
result['log'] = f'E01: types not similar:: {path}, expected {str(_structure)[7:-1] if type(structure)==type(type) else structure.__name__} but got {str(type(instance))[7:-1]}'
|
|
36
41
|
return result
|
|
37
42
|
|
|
@@ -7,7 +7,7 @@ kisa_utils/db.py,sha256=qMOPfBHz9qJIKUwGvSk32EyyhvEFqOpDv0MX4QseXl0,40788
|
|
|
7
7
|
kisa_utils/encryption.py,sha256=KwSUtjZj6m2JqEeeg0GW3bx93PCpEwJlcBzLZrnReyE,3522
|
|
8
8
|
kisa_utils/enqueue.py,sha256=RbImgoPNFFCQHT1ow9zJEM-tHwWE1bNnHznJqEVXL9k,11290
|
|
9
9
|
kisa_utils/figures.py,sha256=ossQHBR7T9rOV1yhQJLDbwrY23xf0RIGOmjcFH7P0ss,1860
|
|
10
|
-
kisa_utils/functionUtils.py,sha256=
|
|
10
|
+
kisa_utils/functionUtils.py,sha256=OPP9by-bpc7DssUFBpNNb0fPrUYxCnji4VlmxswHOrc,5718
|
|
11
11
|
kisa_utils/log.py,sha256=EKBAVvDpY_hgALDCC6i-ARdqQzZwxBxxeHR4NsYgs9U,2120
|
|
12
12
|
kisa_utils/queues.py,sha256=D0bCtI95VEg-xLuzf-Wp0Pfjc5hoEwlmzEJHuokx-i0,5418
|
|
13
13
|
kisa_utils/remote.py,sha256=2EMG2kJudCYqpNPsACe3riQCqTsg-MzviVSZPbjCtxk,1793
|
|
@@ -19,11 +19,11 @@ kisa_utils/token.py,sha256=ReCIBsq95RMYCrDCyHgU1y_Eq-xp_PBpZiHxwsY6Fj4,8015
|
|
|
19
19
|
kisa_utils/permissions/__config__.py,sha256=i3ELkOydDnjKx2ozQTxLZdZ8DXSeUncnl2kRxANjFmM,613
|
|
20
20
|
kisa_utils/permissions/__init__.py,sha256=k7WbNlE8i9Vyf_SdbXbTh8D3gt4obDe3f8rONVVmNH4,36291
|
|
21
21
|
kisa_utils/servers/__init__.py,sha256=lPqDyGTrFo0qwPZ2WA9Xtcpc5D8AIU4huqgFx1iZf68,19
|
|
22
|
-
kisa_utils/servers/flask.py,sha256=
|
|
22
|
+
kisa_utils/servers/flask.py,sha256=niD6Cv04cs6YVMXB7MVjOQ4__78UfLLia7qHdz2FgUs,30354
|
|
23
23
|
kisa_utils/structures/__init__.py,sha256=JBU1j3A42jQ62ALKnsS1Hav9YXcYwjDw1wQJtohXPbU,83
|
|
24
|
-
kisa_utils/structures/utils.py,sha256=
|
|
25
|
-
kisa_utils/structures/validator.py,sha256=
|
|
26
|
-
kisa_utils-0.36.
|
|
27
|
-
kisa_utils-0.36.
|
|
28
|
-
kisa_utils-0.36.
|
|
29
|
-
kisa_utils-0.36.
|
|
24
|
+
kisa_utils/structures/utils.py,sha256=svnzNyVL-wLBEgkovgrqDKvGM4wxYaQGuQ4fFDUR_mo,2515
|
|
25
|
+
kisa_utils/structures/validator.py,sha256=MPPZHXAepBWlF1UFuh5sShkqTzlqANWRLTE42gPz3oA,3647
|
|
26
|
+
kisa_utils-0.36.4.dist-info/METADATA,sha256=kSNW8__fbO_VuZjb5d-eN3WjLE9lr9rFUqijPoKmVKY,477
|
|
27
|
+
kisa_utils-0.36.4.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
|
28
|
+
kisa_utils-0.36.4.dist-info/top_level.txt,sha256=URxY4sRuqmirOxWtztpVmPoGQdksEMYO6hmYsEDGz2Y,75
|
|
29
|
+
kisa_utils-0.36.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|