kisa-utils 0.36.2__py3-none-any.whl → 0.36.3__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.
@@ -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
 
@@ -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
- # print("parameters==>", parameters)
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: kisa-utils
3
- Version: 0.36.2
3
+ Version: 0.36.3
4
4
  Summary: Utility functions and modules for KISA Developers
5
5
  Author: Tom Bukenya
6
6
  Author-email: glayn2bukman@gmail.com
@@ -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=dokWq3OlRplWBZWUdQ5Ev9jKKv3VXRSfBHxvJHP6ylk,5412
10
+ kisa_utils/functionUtils.py,sha256=v8ShUZ56yxdYLMTsxRdj4vQfk6wTo0ATqB1wD1dJYYw,5420
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=0ZepldfHCQ403L_bWuWhXSTHEmiPTxLAhAKYniS-tKY,27952
22
+ kisa_utils/servers/flask.py,sha256=niD6Cv04cs6YVMXB7MVjOQ4__78UfLLia7qHdz2FgUs,30354
23
23
  kisa_utils/structures/__init__.py,sha256=JBU1j3A42jQ62ALKnsS1Hav9YXcYwjDw1wQJtohXPbU,83
24
24
  kisa_utils/structures/utils.py,sha256=doZnnrKT5qGWZIOhXqBnD7mBBc7r-lhwcfpRKcK95Is,2237
25
25
  kisa_utils/structures/validator.py,sha256=2cKaVuY6ia6-pt6o73A6L11qInDz_tygFZdsbZU-RbA,3328
26
- kisa_utils-0.36.2.dist-info/METADATA,sha256=VbYnHxGxowvIc6O_rRFpvaVQYhCpovoEc_-mTi9jAiA,477
27
- kisa_utils-0.36.2.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
28
- kisa_utils-0.36.2.dist-info/top_level.txt,sha256=URxY4sRuqmirOxWtztpVmPoGQdksEMYO6hmYsEDGz2Y,75
29
- kisa_utils-0.36.2.dist-info/RECORD,,
26
+ kisa_utils-0.36.3.dist-info/METADATA,sha256=q8DInAIFbutxylvRMLc-_VibGckjlnXKlwDJv7baeBI,477
27
+ kisa_utils-0.36.3.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
28
+ kisa_utils-0.36.3.dist-info/top_level.txt,sha256=URxY4sRuqmirOxWtztpVmPoGQdksEMYO6hmYsEDGz2Y,75
29
+ kisa_utils-0.36.3.dist-info/RECORD,,