easy-utils-dev 2.172__tar.gz → 2.174__tar.gz

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.
Files changed (45) hide show
  1. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/PKG-INFO +3 -1
  2. easy_utils_dev-2.174/easy_utils_dev/KeycloakAuthExtension.py +178 -0
  3. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/debugger.py +1 -0
  4. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/simple_sqlite.py +26 -9
  5. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/uiserver.py +154 -13
  6. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/wsnoclib.py +26 -0
  7. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev.egg-info/PKG-INFO +3 -1
  8. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev.egg-info/SOURCES.txt +4 -2
  9. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev.egg-info/requires.txt +2 -0
  10. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev.egg-info/top_level.txt +1 -0
  11. easy_utils_dev-2.174/playground/__init__.py +0 -0
  12. easy_utils_dev-2.174/playground/useapi.py +30 -0
  13. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/setup.py +4 -2
  14. easy_utils_dev-2.172/MANIFEST.in +0 -1
  15. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/EasySsh.py +0 -0
  16. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/Events.py +0 -0
  17. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/FastQueue.py +0 -0
  18. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/NameObject.py +0 -0
  19. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/__init__.py +0 -0
  20. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/abortable.py +0 -0
  21. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/brevosmtp.py +0 -0
  22. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/check_license.py +0 -0
  23. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/cplib.py +0 -0
  24. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/custom_env.py +0 -0
  25. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/easy_oracle.py +0 -0
  26. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/encryptor.py +0 -0
  27. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/ept.py +0 -0
  28. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
  29. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
  30. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/exceptions.py +0 -0
  31. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/filescompressor.py +0 -0
  32. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/generate_license.py +0 -0
  33. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/keycloakapi.py +0 -0
  34. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/lralib.py +0 -0
  35. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/ne1830PSS.py +0 -0
  36. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/nsp_kafka.py +0 -0
  37. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/openid_server.py +0 -0
  38. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/optics_utils.py +0 -0
  39. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/require_auth.py +0 -0
  40. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/temp_memory.py +0 -0
  41. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/utils.py +0 -0
  42. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/winserviceapi.py +0 -0
  43. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev/wsselib.py +0 -0
  44. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
  45. {easy_utils_dev-2.172 → easy_utils_dev-2.174}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.172
3
+ Version: 2.174
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -19,6 +19,8 @@ Requires-Dist: pyzipper
19
19
  Requires-Dist: pyjwt
20
20
  Requires-Dist: authlib
21
21
  Requires-Dist: kafka-python
22
+ Requires-Dist: cachetools
23
+ Requires-Dist: js2py
22
24
  Dynamic: classifier
23
25
  Dynamic: keywords
24
26
  Dynamic: requires-dist
@@ -0,0 +1,178 @@
1
+ # decorators.py
2
+ from functools import wraps
3
+ from flask import request
4
+ from authlib.jose import jwt, JsonWebKey
5
+ import requests , time , os , json
6
+ from cachetools import TTLCache, cached
7
+ from easy_utils_dev.utils import start_thread
8
+ from easy_utils_dev.debugger import DEBUGGER
9
+ from easy_utils_dev.utils import getTimestamp
10
+
11
+ class Permissions :
12
+
13
+ cache = TTLCache(maxsize=128, ttl=1800)
14
+
15
+ def __init__(
16
+ self,
17
+ home ,
18
+ keycloak_url ,
19
+ webserver ,
20
+ redirect_url = None ,
21
+ home_url = None,
22
+ realm="lrm",
23
+ **kwargs
24
+ ):
25
+ '''
26
+ home : the home page of the application
27
+ keycloak_url : the keycloak url . like http://localhost:8080
28
+ webserver : the webserver object
29
+ redirect_url : the redirect url
30
+ home_url : the home url
31
+ '''
32
+ self.logger = DEBUGGER(
33
+ name='WebServerAuth',
34
+ homePath=home
35
+ )
36
+ self.keycloak_url = keycloak_url
37
+ self.session = requests.Session()
38
+ self.redirect_url = redirect_url
39
+ self.home = home
40
+ self.home_url = home_url
41
+ self.webserver= webserver
42
+ self.token_refreshed_times = 5
43
+ self.admin_user = 'admin'
44
+ self.admin_password="admin"
45
+ self.client_secret = "yS92obzbpbk36UPsJx4lMx9EtNujkqDk"
46
+ self.realm = realm
47
+ self._thread_login_started = False
48
+ self.KEYCLOACKACCESSTOKEN = None
49
+ self.KEYCLOACKREFRESHTOKEN = None
50
+ self.KEYCLOACKBEARERTOKEN = None
51
+ start_thread(target=self._thread_login)
52
+
53
+
54
+ def _admin_login(self) :
55
+ self.logger.info("Admin login process has started")
56
+ self.logger.debug("Logging in as Keycloak admin ...")
57
+ url = f"{self.keycloak_url}/realms/master/protocol/openid-connect/token"
58
+ payload=f'grant_type=password&client_id=admin-cli&username={self.admin_user}&password={self.admin_password}&client_secret={self.client_secret}&scope=openid profile'
59
+ headers = {
60
+ 'Content-Type': 'application/x-www-form-urlencoded',
61
+ }
62
+ response = requests.request("POST", url, headers=headers, data=payload)
63
+ self.logger.debug(f"Admin login response status: [{response.status_code}]")
64
+ if response.ok :
65
+ self.logger.debug("Admin login successful")
66
+ return response.json()['access_token'] , response.json()['refresh_token']
67
+ self.logger.error(f"Admin login failed: {response.text}")
68
+ raise Exception(f"Login Failure status [{response.status_code}] error={response.text}")
69
+
70
+ def _thread_login(self) :
71
+ self.logger.info("Auto login/refresh process has initialized")
72
+ sleep_period = 1800
73
+ while True :
74
+ self.logger.debug("Auto login process is running now ...")
75
+ if self.token_refreshed_times >= 5 :
76
+ self.KEYCLOACKACCESSTOKEN , self.KEYCLOACKREFRESHTOKEN = self._admin_login()
77
+ self.KEYCLOACKBEARERTOKEN = f"Bearer {self.KEYCLOACKACCESSTOKEN}"
78
+ self.token_refreshed_times = 0
79
+ os.environ.setdefault('KEYCLOACKACCESSTOKEN' , self.KEYCLOACKACCESSTOKEN)
80
+ os.environ.setdefault('KEYCLOACKREFRESHTOKEN' , self.KEYCLOACKREFRESHTOKEN)
81
+ os.environ.setdefault('KEYCLOACKBEARERTOKEN' , self.KEYCLOACKBEARERTOKEN)
82
+ time.sleep(sleep_period)
83
+ continue
84
+ data = {
85
+ "client_id": 'admin-cli',
86
+ "grant_type": "refresh_token",
87
+ "refresh_token": self.KEYCLOACKREFRESHTOKEN,
88
+ 'client_secret' : self.client_secret
89
+ }
90
+ url = f"{self.keycloak_url}/realms/master/protocol/openid-connect/token"
91
+ resp = requests.post(url, data=data)
92
+ self.logger.info(f"Admin token refreshed status [{resp.status_code}]")
93
+ self.KEYCLOACKACCESSTOKEN = resp.json()['access_token']
94
+ self.KEYCLOACKBEARERTOKEN = f"Bearer {self.KEYCLOACKACCESSTOKEN}"
95
+ self.KEYCLOACKREFRESHTOKEN = resp.json()['refresh_token']
96
+ os.environ.setdefault('KEYCLOACKACCESSTOKEN' , self.KEYCLOACKACCESSTOKEN)
97
+ os.environ.setdefault('KEYCLOACKREFRESHTOKEN' , self.KEYCLOACKREFRESHTOKEN)
98
+ os.environ.setdefault('KEYCLOACKBEARERTOKEN' , self.KEYCLOACKBEARERTOKEN)
99
+ self.token_refreshed_times += 1
100
+ time.sleep(sleep_period)
101
+
102
+ def get_jwk_set(self):
103
+ self.logger.debug(f"Getting JWK set for realm {self.realm}")
104
+ jwks_url = f"{self.keycloak_url}/realms/{self.realm}/protocol/openid-connect/certs"
105
+ resp = requests.get(jwks_url)
106
+ self.logger.debug(f'JWK.Cert.Response text: [{resp.text}]')
107
+ resp.raise_for_status()
108
+ self.logger.debug(f"JWK set for realm {self.realm} retrieved successfully")
109
+ return JsonWebKey.import_key_set(resp.json())
110
+
111
+
112
+ def extract_token(self , token):
113
+ self.logger.debug(f'Extracting token for {token}')
114
+ jwks = self.get_jwk_set()
115
+ self.logger.debug(f'JWKS key is {jwks}')
116
+ claims = jwt.decode(token, key=jwks)
117
+ self.logger.debug(f'Extracted claims: {json.dumps(claims , indent=4)}')
118
+ return claims
119
+
120
+ def get_user_info(self , claims) :
121
+ now = getTimestamp()
122
+ if now > claims['exp'] :
123
+ return False
124
+ return True
125
+
126
+ def require_roles(self , *allowed_roles):
127
+ def decorator(fn):
128
+ @wraps(fn)
129
+ def wrapper(*args, **kwargs):
130
+ self.webserver.logger.debug(f"Checking roles for function: {fn.__name__}")
131
+ auth = request.headers.get("Authorization", "")
132
+ if not auth.startswith("Bearer "):
133
+ self.webserver.logger.debug("No Bearer token found in Authorization header")
134
+ return self.webserver.Response.unauthorized(message='Authorization failed' , err="no bearer token found in Authorization header" , home_url=self.home_url , redirect_url=self.redirect_url)
135
+ token = auth.split(" ")[1]
136
+ claims = self.extract_token(token)
137
+ self.logger.debug(f"Extracted claims: {claims}")
138
+ if not claims:
139
+ self.webserver.logger.debug(f'Invalid or expired token. will ask client to redirect to "{self.redirect_url}"')
140
+ return self.webserver.Response.unauthorized(message='Invalid or expired token' , err="invalid or expired token" , home_url=self.home_url , redirect_url=self.redirect_url)
141
+ user_roles = claims.get("roles", [])
142
+ if not any(role in user_roles for role in allowed_roles):
143
+ self.webserver.logger.debug(f'Insufficient permissions. will ask client to redirect to "{self.redirect_url}"')
144
+ return self.webserver.Response.unauthorized(message='Insufficient permissions' , err="insufficient permissions" , home_url=self.home_url , redirect_url=self.redirect_url)
145
+ # Inject user info into request context
146
+ request.user_info = claims
147
+ self.webserver.logger.debug(f"Access granted for {fn.__name__} with roles: {user_roles}")
148
+ return fn(*args, **kwargs)
149
+ return wrapper
150
+ return decorator
151
+
152
+ def check_access(self):
153
+ def decorator(fn):
154
+ @wraps(fn)
155
+ def wrapper(*args, **kwargs):
156
+ self.webserver.logger.debug(f"Checking access for function: {fn.__name__}")
157
+ auth = request.headers.get("Authorization", "")
158
+ email = request.headers.get("email", "")
159
+ if not auth.startswith("Bearer "):
160
+ self.webserver.logger.debug("No Bearer token found in Authorization header")
161
+ return self.webserver.Response.unauthorized(message='Authorization failed' , err="no bearer token found in Authorization header" , home_url=self.home_url , redirect_url=self.redirect_url)
162
+ token = auth.split(" ")[1]
163
+ claims = self.extract_token(token)
164
+ if not claims :
165
+ self.webserver.logger.debug("Invalid or expired token. will ask client to redirect to \"{self.redirect_url}\"")
166
+ return self.webserver.Response.unauthorized(message='Invalid or expired token' , err="invalid or expired token" , home_url=self.home_url , redirect_url=self.redirect_url)
167
+ valid = self.get_user_info(claims)
168
+ if not valid :
169
+ self.webserver.logger.debug("User info validation failed for token")
170
+ return self.webserver.Response.unauthorized(message='Authorization failed' , err="user info validation failed for token" , home_url=self.home_url , redirect_url=self.redirect_url)
171
+ # Inject user info into request context
172
+ request.user_info = claims
173
+ self.webserver.logger.debug(f"Access granted for email={email}")
174
+ return fn(*args, **kwargs)
175
+ return wrapper
176
+ return decorator
177
+
178
+
@@ -89,6 +89,7 @@ class DEBUGGER:
89
89
  self.LOG_SIZE_THRESHOLD_IN_BYTES = 10 * 1024 * 1024
90
90
  self.BACKUP_COUNT = log_rotation
91
91
  self.name = name
92
+ self._home = homePath
92
93
  self.create_log_path(homePath, file_name)
93
94
  self.fullSyntax=fullSyntax
94
95
  self.onScreen= onscreen
@@ -2,7 +2,7 @@ import sqlite3 , os ,time
2
2
  from .debugger import DEBUGGER
3
3
  from .utils import getRandomKey
4
4
  from .custom_env import custom_env , setupEnvironment
5
- from .utils import fixTupleForSql
5
+ from .utils import fixTupleForSql, start_thread
6
6
 
7
7
  env = custom_env()
8
8
 
@@ -25,6 +25,7 @@ class initDB :
25
25
  self.lastCur = None
26
26
  self.lastCon=None
27
27
  self.id = id
28
+ self.optimized = False
28
29
  self.WAL_JOURNAL = WAL_JOURNAL
29
30
  self.applyDatabaseParams=True
30
31
  if not loggerName :
@@ -49,14 +50,25 @@ class initDB :
49
50
  results = tuple(list)
50
51
  return results
51
52
 
52
- def db_connect(self, WAL=False , timeout: int=30):
53
+ def optimize_database_for_multi_threading(self) :
54
+ cur , con = self.db_connect(db_optimize=False)
55
+ cur.execute('PRAGMA journal_mode = WAL;')
56
+ cur.execute('PRAGMA busy_timeout = 10000;')
57
+ cur.execute('PRAGMA wal_autocheckpoint = 10000;')
58
+ cur.execute('PRAGMA temp_store = MEMORY;')
59
+ cur.execute('PRAGMA fullfsync = OFF;')
60
+ cur.execute('PRAGMA wal_checkpoint_fullfsync = OFF;')
61
+ con.commit()
62
+ con.close()
63
+
64
+ def db_connect(self, WAL=False , db_optimize = True , timeout: int=30):
53
65
  self.logger.debug(f'connecting to {self.db_file}')
54
- self.con = sqlite3.connect(self.db_file , check_same_thread=False,timeout= timeout)
55
- self.cur = self.con.cursor()
56
- if WAL or self.WAL_JOURNAL :
57
- self.con.execute("PRAGMA journal_mode=WAL;")
58
- self.con.commit()
59
- return self.cur , self.con
66
+ con= sqlite3.connect(self.db_file , check_same_thread=False,timeout= timeout)
67
+ cur = con.cursor()
68
+ if not self.optimized and db_optimize :
69
+ self.optimize_database_for_multi_threading()
70
+ self.optimized = True
71
+ return cur , con
60
72
 
61
73
  def execute_dict(self,cli:str) :
62
74
  self.logger.debug(f"execute_dict: {cli}")
@@ -69,10 +81,15 @@ class initDB :
69
81
  conn.close()
70
82
  return h
71
83
 
72
- def vacuum(self) :
84
+ def vacuum(self , table=None) :
73
85
  try :
74
86
  cur , conn = self.db_connect()
75
87
  cur.execute('VACUUM')
88
+ cur.execute("PRAGMA optimize")
89
+ if table :
90
+ cur.execute(f"update sqlite_sequence set seq=0 where name='{table}'")
91
+ else :
92
+ cur.execute(f"update sqlite_sequence set seq=0")
76
93
  conn.commit()
77
94
  conn.close()
78
95
  except :
@@ -1,35 +1,34 @@
1
1
  import gc
2
2
  import json
3
3
  import time
4
- from flask.ctx import F
4
+ import logging
5
+ import os
6
+ import traceback
7
+ import threading
8
+ import signal
9
+ import sys
5
10
  from werkzeug.serving import ThreadedWSGIServer
6
11
  from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
7
12
  from flask_socketio import SocketIO
8
13
  from engineio.async_drivers import gevent
9
14
  from engineio.async_drivers import threading as threading_engineio
10
15
  from flask_cors import CORS
11
- import logging , os
12
- from flask import jsonify, request , current_app , copy_current_request_context
16
+ from flask import request , current_app , copy_current_request_context
13
17
  from flask import Flask
14
18
  from threading import Thread
15
- import threading
16
19
  from easy_utils_dev.custom_env import cenv
17
20
  from easy_utils_dev.utils import kill_thread
18
- from multiprocessing import Process
19
- import traceback
20
21
  from werkzeug.serving import make_ssl_devcert
21
22
  from time import sleep
22
- from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs , lget
23
+ from easy_utils_dev.utils import start_thread , getRandomKeysAndStr
23
24
  from easy_utils_dev.temp_memory import TemporaryMemory
24
25
  from easy_utils_dev.debugger import DEBUGGER
25
- import signal
26
- import sys
27
26
  from tempfile import gettempdir
28
27
  from urllib.parse import urlparse, parse_qs
28
+ from easy_utils_dev.KeycloakAuthExtension import Permissions
29
29
 
30
30
  TMP_PATH = gettempdir()
31
31
 
32
-
33
32
  def extract_buid(url: str , key ) -> str | None:
34
33
  """
35
34
  Extracts the 'buid' query parameter from a URL.
@@ -224,6 +223,7 @@ class SocketClientObject :
224
223
  self.browserid = None
225
224
 
226
225
  class UISERVER :
226
+
227
227
  def __init__(self ,
228
228
  logger : DEBUGGER = None,
229
229
  id=getRandomKey(n=15),
@@ -236,9 +236,73 @@ class UISERVER :
236
236
  ssl_key=None,
237
237
  template_folder='templates/' ,
238
238
  static_folder = 'templates/assets',
239
- socketio_async_mode = "threading"
240
- ,**kwargs
239
+ socketio_async_mode = "threading",
240
+ keycloak_integration=False,
241
+ **kwargs
241
242
  ) -> None:
243
+ """
244
+ Initialize the application server configuration.
245
+
246
+ Parameters
247
+ ----------
248
+ logger : DEBUGGER, optional
249
+ Custom logger instance used for application logging.
250
+ If None, default logger behavior is used.
251
+
252
+ id : str, optional
253
+ Unique identifier for the application instance.
254
+ Generated automatically if not provided.
255
+
256
+ secretkey : str, optional
257
+ Secret key used for internal authentication, sessions,
258
+ or secure operations. Auto-generated by default.
259
+
260
+ serve_with_secret_key : bool, optional
261
+ If True, the server will require the secret key
262
+ for certain protected operations or endpoints.
263
+
264
+ address : str, optional
265
+ Host address to bind the server to.
266
+ Default is 'localhost'.
267
+
268
+ port : int, optional
269
+ Port number the server will listen on.
270
+ Default is 5312.
271
+
272
+ https : bool, optional
273
+ Enable HTTPS support.
274
+ If True, ssl_crt and ssl_key must be provided.
275
+
276
+ ssl_crt : str or None, optional
277
+ Path to the SSL certificate file (PEM/CRT).
278
+ Required if https=True.
279
+
280
+ ssl_key : str or None, optional
281
+ Path to the SSL private key file.
282
+ Required if https=True.
283
+
284
+ template_folder : str, optional
285
+ Path to the templates directory used by the web framework.
286
+
287
+ static_folder : str, optional
288
+ Path to the static assets directory (CSS, JS, images).
289
+
290
+ socketio_async_mode : str, optional
291
+ Async mode used by Socket.IO.
292
+ Common values: 'threading', 'eventlet', 'gevent'.
293
+
294
+ keycloak_integration : bool, optional
295
+ Enable Keycloak authentication integration if True.
296
+
297
+ **kwargs : dict
298
+ Additional keyword arguments passed to the underlying
299
+ framework or server configuration.
300
+
301
+ Notes
302
+ -----
303
+ - HTTPS requires valid SSL certificate and key.
304
+ - Secret keys should be kept secure in production environments.
305
+ """
242
306
  self.id = id
243
307
  self.static_folder = static_folder
244
308
  self.app = app = Flask(self.id , template_folder=template_folder , static_folder=self.static_folder )
@@ -289,8 +353,19 @@ class UISERVER :
289
353
  name='webserver',
290
354
  homePath=TMP_PATH
291
355
  )
356
+ if keycloak_integration :
357
+ self.auth = Permissions(
358
+ home=self.logger._home ,
359
+ redirect_url=kwargs.get('keycloak_redirect_url') ,
360
+ keycloak_url=kwargs.get('keycloak_home_url') ,
361
+ callback_url=kwargs.get('keycloak_callback_url'),
362
+ webserver=self
363
+ )
364
+ else :
365
+ self.auth = None
292
366
  self.start_before_request()
293
367
 
368
+
294
369
  def update_cert(self , crt, ssl ) :
295
370
  self.ssl_crt=crt
296
371
  self.ssl_key=ssl
@@ -624,4 +699,70 @@ class UISERVER :
624
699
  except:
625
700
  pass
626
701
  kill_thread(self.thread)
627
- return True
702
+ return True
703
+ class WebServer(UISERVER):
704
+ def __init__(self , *args , **kwargs) :
705
+ """
706
+ Initialize the application server configuration.
707
+
708
+ Parameters
709
+ ----------
710
+ logger : DEBUGGER, optional
711
+ Custom logger instance used for application logging.
712
+ If None, default logger behavior is used.
713
+
714
+ id : str, optional
715
+ Unique identifier for the application instance.
716
+ Generated automatically if not provided.
717
+
718
+ secretkey : str, optional
719
+ Secret key used for internal authentication, sessions,
720
+ or secure operations. Auto-generated by default.
721
+
722
+ serve_with_secret_key : bool, optional
723
+ If True, the server will require the secret key
724
+ for certain protected operations or endpoints.
725
+
726
+ address : str, optional
727
+ Host address to bind the server to.
728
+ Default is 'localhost'.
729
+
730
+ port : int, optional
731
+ Port number the server will listen on.
732
+ Default is 5312.
733
+
734
+ https : bool, optional
735
+ Enable HTTPS support.
736
+ If True, ssl_crt and ssl_key must be provided.
737
+
738
+ ssl_crt : str or None, optional
739
+ Path to the SSL certificate file (PEM/CRT).
740
+ Required if https=True.
741
+
742
+ ssl_key : str or None, optional
743
+ Path to the SSL private key file.
744
+ Required if https=True.
745
+
746
+ template_folder : str, optional
747
+ Path to the templates directory used by the web framework.
748
+
749
+ static_folder : str, optional
750
+ Path to the static assets directory (CSS, JS, images).
751
+
752
+ socketio_async_mode : str, optional
753
+ Async mode used by Socket.IO.
754
+ Common values: 'threading', 'eventlet', 'gevent'.
755
+
756
+ keycloak_integration : bool, optional
757
+ Enable Keycloak authentication integration if True.
758
+
759
+ **kwargs : dict
760
+ Additional keyword arguments passed to the underlying
761
+ framework or server configuration.
762
+
763
+ Notes
764
+ -----
765
+ - HTTPS requires valid SSL certificate and key.
766
+ - Secret keys should be kept secure in production environments.
767
+ """
768
+ super().__init__(*args , **kwargs)
@@ -133,6 +133,32 @@ class WSNOCLIB :
133
133
  def getSession(self) :
134
134
  return self.session
135
135
 
136
+ def getZicCardView(self,neName , return_dict=True , return_json=False ) :
137
+ import js2py
138
+ cookies = {
139
+ "ycsm": f"token%3DWebSessionId%3D{self.access_token}",
140
+ "EQMWebUITimeoutCookie": "15",
141
+ }
142
+ response = self.session.get(f"https://{self.address}:9213/{neName}/scripts/cardview.js", cookies=cookies)
143
+ js = originalJs = response.text
144
+ if len(js) < 1000 :
145
+ self.logger.error(f'Failed to get cardview.js. Response: {response.text}')
146
+ return None
147
+ if return_dict or return_json :
148
+ js = "var isOMSUser = true;" + js
149
+ js = js + """
150
+ function getData(){
151
+ return JSON.stringify(CARDOBJECT)
152
+ }"""
153
+ js =js.replace('new Array();', 'Object.create(null);')
154
+ ctx = js2py.EvalJs()
155
+ ctx.execute(js)
156
+ cards = ctx.getData()
157
+ if return_json :
158
+ return cards
159
+ return json.loads(cards)
160
+ return originalJs
161
+
136
162
  def connect(self,auto_refresh_token=True) -> dict :
137
163
  self.auto_refresh_token = auto_refresh_token
138
164
  #refresh the session
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: easy_utils_dev
3
- Version: 2.172
3
+ Version: 2.174
4
4
  Keywords: python3
5
5
  Classifier: Programming Language :: Python :: 3
6
6
  Requires-Dist: psutil
@@ -19,6 +19,8 @@ Requires-Dist: pyzipper
19
19
  Requires-Dist: pyjwt
20
20
  Requires-Dist: authlib
21
21
  Requires-Dist: kafka-python
22
+ Requires-Dist: cachetools
23
+ Requires-Dist: js2py
22
24
  Dynamic: classifier
23
25
  Dynamic: keywords
24
26
  Dynamic: requires-dist
@@ -1,8 +1,8 @@
1
- MANIFEST.in
2
1
  setup.py
3
2
  easy_utils_dev/EasySsh.py
4
3
  easy_utils_dev/Events.py
5
4
  easy_utils_dev/FastQueue.py
5
+ easy_utils_dev/KeycloakAuthExtension.py
6
6
  easy_utils_dev/NameObject.py
7
7
  easy_utils_dev/__init__.py
8
8
  easy_utils_dev/abortable.py
@@ -37,4 +37,6 @@ easy_utils_dev.egg-info/dependency_links.txt
37
37
  easy_utils_dev.egg-info/requires.txt
38
38
  easy_utils_dev.egg-info/top_level.txt
39
39
  easy_utils_dev/ept_sql/create_dirs.sql
40
- easy_utils_dev/ept_sql/create_ept_tables.sql
40
+ easy_utils_dev/ept_sql/create_ept_tables.sql
41
+ playground/__init__.py
42
+ playground/useapi.py
@@ -14,3 +14,5 @@ pyzipper
14
14
  pyjwt
15
15
  authlib
16
16
  kafka-python
17
+ cachetools
18
+ js2py
File without changes
@@ -0,0 +1,30 @@
1
+ # Add the parent directory to sys.path to allow importing easy_utils_dev
2
+ import sys , os
3
+ sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
4
+ from flask import request
5
+ from easy_utils_dev import utils
6
+ from easy_utils_dev.uiserver import WebServer
7
+
8
+ server = WebServer(
9
+ address='127.0.0.1',
10
+ port=3001,
11
+ )
12
+
13
+ app = server.app
14
+
15
+ @app.route('/api/users' , methods=['GET'])
16
+ def _api_get_users():
17
+ return server.Response.ok(users=[{'name': 'John Doe', 'age': 30}, {'name': 'Jane Doe', 'age': 25}])
18
+
19
+ @app.route('/api/checktoken' , methods=['GET'])
20
+ def api_checktoken():
21
+ print(request.headers.get('Authorization'))
22
+ return server.Response.ok()
23
+
24
+ @app.route('/api/notify' , methods=['GET'])
25
+ def api_notify_test():
26
+ return server.Response.ok(toast=True , message=utils.getTimestamp())
27
+
28
+
29
+ if __name__ == "__main__":
30
+ server.startUi(block=True)
@@ -1,6 +1,6 @@
1
1
  from setuptools import setup, find_packages
2
2
 
3
- VERSION = '2.172'
3
+ VERSION = '2.174'
4
4
 
5
5
  # Setting up
6
6
  setup(
@@ -24,7 +24,9 @@ setup(
24
24
  'pyzipper',
25
25
  'pyjwt',
26
26
  'authlib',
27
- 'kafka-python'
27
+ 'kafka-python',
28
+ 'cachetools',
29
+ 'js2py'
28
30
  ],
29
31
  keywords=['python3'],
30
32
  classifiers=[
@@ -1 +0,0 @@
1
- recursive-include easy_utils_dev/ept_sql *.sql
File without changes