easy-utils-dev 2.155__tar.gz → 2.156__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.
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/PKG-INFO +1 -1
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/debugger.py +12 -8
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/ne1830PSS.py +16 -2
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/uiserver.py +118 -11
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/PKG-INFO +1 -1
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/setup.py +1 -1
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/MANIFEST.in +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/EasySsh.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/Events.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/FastQueue.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/NameObject.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/__init__.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/abortable.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/brevosmtp.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/check_license.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/cplib.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/custom_env.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/debugger-C-PF4PAMMP.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/easy_oracle.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/encryptor.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/ept.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/ept_sql/create_dirs.sql +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/ept_sql/create_ept_tables.sql +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/exceptions.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/filescompressor.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/generate_license.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/keycloakapi.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/lralib.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/nsp_kafka.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/openid_server.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/optics_utils.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/require_auth.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/simple_sqlite.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/temp_memory.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/utils.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/winserviceapi.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/wsnoclib.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev/wsselib.py +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/SOURCES.txt +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/dependency_links.txt +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/requires.txt +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/easy_utils_dev.egg-info/top_level.txt +0 -0
- {easy_utils_dev-2.155 → easy_utils_dev-2.156}/setup.cfg +0 -0
|
@@ -45,7 +45,8 @@ class DEBUGGER:
|
|
|
45
45
|
global_debugger=None,
|
|
46
46
|
disable_log_write=False,
|
|
47
47
|
file_name=None,
|
|
48
|
-
seperate_files=True
|
|
48
|
+
seperate_files=True,
|
|
49
|
+
trust_env_log_path=True
|
|
49
50
|
):
|
|
50
51
|
env = custom_env()
|
|
51
52
|
setupEnvironment( 'debugger' )
|
|
@@ -63,6 +64,7 @@ class DEBUGGER:
|
|
|
63
64
|
self.create_log_path(homePath, file_name)
|
|
64
65
|
self.fullSyntax=fullSyntax
|
|
65
66
|
self.onScreen= onscreen
|
|
67
|
+
self.trust_env_log_path = trust_env_log_path
|
|
66
68
|
self.id = id
|
|
67
69
|
self.stream_service = None
|
|
68
70
|
if not env['debugger'].get(name) :
|
|
@@ -118,8 +120,9 @@ class DEBUGGER:
|
|
|
118
120
|
start_thread(target=self.checks_in_bg)
|
|
119
121
|
|
|
120
122
|
def create_log_path(self , base_path , logname ) :
|
|
123
|
+
# print(f"Creating log path : {base_path} {logname}")
|
|
121
124
|
if not base_path :
|
|
122
|
-
base_path = os.
|
|
125
|
+
base_path = os.getcwd()
|
|
123
126
|
if not logname :
|
|
124
127
|
logname = self.name
|
|
125
128
|
if base_path :
|
|
@@ -128,10 +131,7 @@ class DEBUGGER:
|
|
|
128
131
|
self.filename = logname
|
|
129
132
|
self.lastAbsoluteHomePath = base_path
|
|
130
133
|
self.log_path_with_filename = os.path.join(base_path, f'{logname}.log')
|
|
131
|
-
|
|
132
|
-
# print(f"Base Home Path : {self.baseHomePath}")
|
|
133
|
-
# print(f"Log path With Filename : {self.log_path_with_filename}")
|
|
134
|
-
# print(f"Last Absolute Home Path : {self.lastAbsoluteHomePath}")
|
|
134
|
+
|
|
135
135
|
|
|
136
136
|
def fix_file_not_found_file_rotation(self) :
|
|
137
137
|
for i in range(self.BACKUP_COUNT) :
|
|
@@ -165,6 +165,8 @@ class DEBUGGER:
|
|
|
165
165
|
self.console.setFormatter(self.formatter)
|
|
166
166
|
|
|
167
167
|
def updateGlobalHomePath(self ) :
|
|
168
|
+
if not self.trust_env_log_path :
|
|
169
|
+
return
|
|
168
170
|
if not self.isLogWriteDisabled :
|
|
169
171
|
getFromEnv = self.env.get('debugger_homepath' , None )
|
|
170
172
|
# print(f"getFromEnv : {getFromEnv}")
|
|
@@ -273,7 +275,8 @@ class DEBUGGER:
|
|
|
273
275
|
self.logger.error(f'EASY_UTILS_DEBUG_LEVEL ENV must be one of [info,debug,warning,error,critical] | Current Env Variable Is "{EASY_UTILS_DEBUG_LEVEL}". Skipping ')
|
|
274
276
|
else :
|
|
275
277
|
self.set_level(EASY_UTILS_DEBUG_LEVEL)
|
|
276
|
-
self.
|
|
278
|
+
if self.trust_env_log_path :
|
|
279
|
+
self.updateGlobalHomePath()
|
|
277
280
|
if os.environ.get("EASY_UTILS_ENABLE_PRINT" , '' ).lower() == 'true' :
|
|
278
281
|
self.enable_print()
|
|
279
282
|
sleep(10)
|
|
@@ -314,12 +317,13 @@ class DEBUGGER:
|
|
|
314
317
|
def changeHomePath( self , path ) :
|
|
315
318
|
def delet_later(lastAbsoluteHomePath) :
|
|
316
319
|
if lastAbsoluteHomePath :
|
|
317
|
-
sleep(
|
|
320
|
+
sleep(1)
|
|
318
321
|
try :
|
|
319
322
|
shutil.rmtree(lastAbsoluteHomePath)
|
|
320
323
|
except :
|
|
321
324
|
pass
|
|
322
325
|
start_thread(target=delet_later, args=[self.lastAbsoluteHomePath])
|
|
326
|
+
sleep(.5)
|
|
323
327
|
self.create_log_path( path , self.filename)
|
|
324
328
|
self.file_handler_class = self.createRotateFileHandler(self.log_path_with_filename)
|
|
325
329
|
|
|
@@ -12,9 +12,23 @@ from easy_utils_dev import exceptions
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class PSS1830 :
|
|
15
|
-
def __init__(self ,
|
|
15
|
+
def __init__(self ,
|
|
16
|
+
sim=False ,
|
|
17
|
+
debug_name='Auto1830PSS' ,
|
|
18
|
+
auto_enable_tcp_forward=False,
|
|
19
|
+
file_name=None,
|
|
20
|
+
debug_home_path=None,
|
|
21
|
+
trust_env_log_path=True,
|
|
22
|
+
debugger_kwargs={}
|
|
23
|
+
) -> None:
|
|
16
24
|
self.port = None
|
|
17
|
-
self.
|
|
25
|
+
self.trust_env_log_path = trust_env_log_path
|
|
26
|
+
self.logger = DEBUGGER(
|
|
27
|
+
debug_name,file_name=file_name,
|
|
28
|
+
homePath=debug_home_path,
|
|
29
|
+
trust_env_log_path=trust_env_log_path,
|
|
30
|
+
**debugger_kwargs
|
|
31
|
+
)
|
|
18
32
|
self.connected = False
|
|
19
33
|
self.channel = None
|
|
20
34
|
self.nodeName = None
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import gc
|
|
2
|
+
import json
|
|
2
3
|
import time
|
|
4
|
+
from flask.ctx import F
|
|
3
5
|
from werkzeug.serving import ThreadedWSGIServer
|
|
4
|
-
from easy_utils_dev.utils import getRandomKey , generateToken , getTimestamp
|
|
6
|
+
from easy_utils_dev.utils import convertTimestampToDate, getRandomKey , generateToken , getTimestamp
|
|
5
7
|
from flask_socketio import SocketIO
|
|
6
8
|
from engineio.async_drivers import gevent
|
|
9
|
+
from engineio.async_drivers import threading as threading_engineio
|
|
7
10
|
from flask_cors import CORS
|
|
8
11
|
import logging , os
|
|
9
12
|
from flask import jsonify, request , current_app , copy_current_request_context
|
|
@@ -18,6 +21,9 @@ from werkzeug.serving import make_ssl_devcert
|
|
|
18
21
|
from time import sleep
|
|
19
22
|
from easy_utils_dev.utils import start_thread , getRandomKeysAndStr , mkdirs
|
|
20
23
|
from easy_utils_dev.temp_memory import TemporaryMemory
|
|
24
|
+
from easy_utils_dev.debugger import DEBUGGER
|
|
25
|
+
import signal
|
|
26
|
+
import sys
|
|
21
27
|
|
|
22
28
|
def getClassById( id ) :
|
|
23
29
|
return cenv[id]
|
|
@@ -92,6 +98,7 @@ class SocketClientObject :
|
|
|
92
98
|
|
|
93
99
|
class UISERVER :
|
|
94
100
|
def __init__(self ,
|
|
101
|
+
logger : DEBUGGER = None,
|
|
95
102
|
id=getRandomKey(n=15),
|
|
96
103
|
secretkey=generateToken(),
|
|
97
104
|
serve_with_secret_key=False,
|
|
@@ -121,6 +128,7 @@ class UISERVER :
|
|
|
121
128
|
self.bg_requests = {}
|
|
122
129
|
self.socketio_clients = []
|
|
123
130
|
self.abort_base_url = '/request/abort'
|
|
131
|
+
self.return_exception_as_code_400 = True
|
|
124
132
|
self.request_reply_base_url= '/request/result'
|
|
125
133
|
if https :
|
|
126
134
|
self.httpProtocol = 'https'
|
|
@@ -133,6 +141,10 @@ class UISERVER :
|
|
|
133
141
|
start_thread(target=self.delete_very_old_requests)
|
|
134
142
|
self.secret_key_execlude_urls = []
|
|
135
143
|
self.socketio_rooms = {}
|
|
144
|
+
self.log_url_requests = True
|
|
145
|
+
self.logger = logger
|
|
146
|
+
self.simulate_network_delay = False
|
|
147
|
+
|
|
136
148
|
|
|
137
149
|
def update_cert(self , crt, ssl ) :
|
|
138
150
|
self.ssl_crt=crt
|
|
@@ -158,6 +170,7 @@ class UISERVER :
|
|
|
158
170
|
|
|
159
171
|
def delete_very_old_requests(self) :
|
|
160
172
|
while True :
|
|
173
|
+
sleep(320)
|
|
161
174
|
now = getTimestamp()
|
|
162
175
|
for key, value in list(self.bg_requests.items()) :
|
|
163
176
|
value : AbortRequest = value
|
|
@@ -165,7 +178,6 @@ class UISERVER :
|
|
|
165
178
|
if value.delete_async_request_ts > now :
|
|
166
179
|
del self.bg_requests[key]
|
|
167
180
|
gc.collect()
|
|
168
|
-
sleep(320)
|
|
169
181
|
|
|
170
182
|
def create_room(self , room_id : str , members : list[str] ) :
|
|
171
183
|
self.socketio_rooms[room_id] = members
|
|
@@ -197,6 +209,14 @@ class UISERVER :
|
|
|
197
209
|
else :
|
|
198
210
|
return { 'status' : 404 , 'message' : 'Request not found or request is not abortable. Check request headers for abortable flag.'}
|
|
199
211
|
|
|
212
|
+
@self.app.route(f"/request/traceback/<key>" , methods=['GET'])
|
|
213
|
+
def get_traceback(key : str ) :
|
|
214
|
+
traceback = self.cache.get(key)
|
|
215
|
+
if traceback :
|
|
216
|
+
return { 'status' : 200 , 'traceback' : traceback.get('traceback') }
|
|
217
|
+
else :
|
|
218
|
+
return { 'status' : 404 , 'message' : 'Traceback not found or expired' }
|
|
219
|
+
|
|
200
220
|
@self.app.route(f'{self.request_reply_base_url}/<id>' , methods=['GET'])
|
|
201
221
|
def get_result_of_async_request(id : str ) :
|
|
202
222
|
request : AbortRequest = self.bg_requests.get(id)
|
|
@@ -217,6 +237,10 @@ class UISERVER :
|
|
|
217
237
|
|
|
218
238
|
@self.app.before_request
|
|
219
239
|
def before_request() :
|
|
240
|
+
if self.log_url_requests and self.logger :
|
|
241
|
+
self.logger.info(f'[{request.method}]: {request.url}' , source='WebServer')
|
|
242
|
+
|
|
243
|
+
|
|
220
244
|
if (self.serve_with_secret_key) and (request.path not in self.secret_key_execlude_urls) and (request.headers.get('secretkey') != self.secretkey):
|
|
221
245
|
return jsonify({"error": "Secret key is invalid"}), 401
|
|
222
246
|
|
|
@@ -257,26 +281,72 @@ class UISERVER :
|
|
|
257
281
|
if request.headers.get('async') == 'false' :
|
|
258
282
|
target_func = current_app.view_functions.get(request.endpoint)
|
|
259
283
|
if not target_func:
|
|
260
|
-
return
|
|
284
|
+
return {"error": "Route not found"}, 404
|
|
261
285
|
th = start_thread(target=run_async_job_results, args=[target_func , abort ])
|
|
262
286
|
abort.thread = th
|
|
263
287
|
self.bg_requests[requestId] = abort
|
|
264
288
|
return {"status": 200, "message": "Request now in running bg", "abort_id": abort.abort_id} , 200
|
|
265
289
|
|
|
290
|
+
if self.return_exception_as_code_400 :
|
|
291
|
+
@self.app.errorhandler(Exception)
|
|
292
|
+
def handle_exception(e):
|
|
293
|
+
|
|
294
|
+
exc_type, exc_value, exc_traceback = sys.exc_info()
|
|
295
|
+
key = getRandomKeysAndStr(n=10)
|
|
296
|
+
tb_last = traceback.extract_tb(exc_traceback)[-1] # Get last traceback frame
|
|
297
|
+
# Example: file, line, function, text
|
|
298
|
+
error_file = tb_last.filename
|
|
299
|
+
error_line = tb_last.lineno
|
|
300
|
+
error_func = tb_last.name
|
|
301
|
+
error_code = tb_last.line
|
|
302
|
+
|
|
303
|
+
# Log the full traceback (optional)
|
|
304
|
+
traceback.print_exc()
|
|
305
|
+
# Customize the error response
|
|
306
|
+
t = getTimestamp()
|
|
307
|
+
response = {
|
|
308
|
+
"status" : 400 ,
|
|
309
|
+
"key" : key,
|
|
310
|
+
"error": str(e),
|
|
311
|
+
"message": str(e),
|
|
312
|
+
"type": type(e).__name__ ,
|
|
313
|
+
"request_full_url" : request.url ,
|
|
314
|
+
"request_method" : request.method ,
|
|
315
|
+
"endpoint" : request.endpoint ,
|
|
316
|
+
"request_path" : request.path ,
|
|
317
|
+
"error_file" : os.path.basename(error_file).replace('.py' , ''),
|
|
318
|
+
"error_line" : error_line,
|
|
319
|
+
"error_func" : error_func,
|
|
320
|
+
"error_code" : error_code,
|
|
321
|
+
'timestamp' : t ,
|
|
322
|
+
"date" : convertTimestampToDate(t) ,
|
|
323
|
+
'traceback_url' : f"/request/traceback/{key}"
|
|
324
|
+
}
|
|
325
|
+
self.logger.error(f'error: {json.dumps(response , indent=4)}')
|
|
326
|
+
self.cache.set( custom_key=key , item={**response , 'traceback' : str(traceback.format_exc())} , auto_destroy_period=1800 , store_deleted_key=False )
|
|
327
|
+
# del response['traceback']
|
|
328
|
+
return response , 200
|
|
266
329
|
|
|
267
330
|
@self.app.after_request
|
|
268
331
|
def after_request(response) :
|
|
332
|
+
|
|
333
|
+
if self.simulate_network_delay :
|
|
334
|
+
time.sleep(self.simulate_network_delay)
|
|
335
|
+
|
|
269
336
|
try :
|
|
270
337
|
now = getTimestamp()
|
|
271
338
|
response.headers['internalid'] = request.internalid
|
|
272
339
|
response.headers['start_ts'] = request.start_ts
|
|
273
340
|
response.headers['end_ts'] = now
|
|
274
|
-
|
|
341
|
+
x = round(now - request.start_ts, 2)
|
|
342
|
+
response.headers['execution'] = x
|
|
275
343
|
if request.abortable :
|
|
276
344
|
response.headers['abortid'] = request.abort_id
|
|
277
345
|
response.headers['abortable'] = True
|
|
278
346
|
except :
|
|
279
347
|
response.headers['abortable'] = False
|
|
348
|
+
if self.log_url_requests and self.logger :
|
|
349
|
+
self.logger.info(f'[{request.method}]: {request.url} - [{response.status_code}] [secs:{x}]' , source='WebServer')
|
|
280
350
|
return response
|
|
281
351
|
|
|
282
352
|
# socketio client connected.
|
|
@@ -322,12 +392,18 @@ class UISERVER :
|
|
|
322
392
|
return self.wsgi_server
|
|
323
393
|
|
|
324
394
|
def shutdownUi(self) :
|
|
395
|
+
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
396
|
+
try:
|
|
397
|
+
self.wsgi_server.shutdown()
|
|
398
|
+
self.wsgi_server.server_close()
|
|
399
|
+
except:
|
|
400
|
+
pass
|
|
325
401
|
kill_thread(self.thread)
|
|
326
|
-
self.wsgi_server.server_close()
|
|
327
|
-
self.wsgi_server.shutdown()
|
|
328
402
|
|
|
329
403
|
def _wait_th(self , t ) :
|
|
330
|
-
t.join()
|
|
404
|
+
# t.join()
|
|
405
|
+
while True :
|
|
406
|
+
time.sleep(10)
|
|
331
407
|
|
|
332
408
|
|
|
333
409
|
def thrStartUi(self , suppress_prints=True) :
|
|
@@ -356,15 +432,46 @@ class UISERVER :
|
|
|
356
432
|
log.setLevel(logging.ERROR)
|
|
357
433
|
wsgi_server.serve_forever()
|
|
358
434
|
|
|
359
|
-
def
|
|
435
|
+
def on_ctrl_c(self , sig=None, frame=None):
|
|
436
|
+
self.stopUi()
|
|
437
|
+
pid = os.getpid()
|
|
438
|
+
os.kill(pid, signal.SIGTERM)
|
|
439
|
+
sys.exit(0)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def startUi(self , daemon=False , suppress_prints=True , block=False) :
|
|
360
443
|
self.start_before_request()
|
|
361
444
|
self.thread = self.flaskprocess = Thread(target=self.thrStartUi , args=[suppress_prints])
|
|
362
|
-
self.flaskprocess.daemon =
|
|
445
|
+
self.flaskprocess.daemon = daemon
|
|
363
446
|
self.flaskprocess.start()
|
|
364
447
|
start_thread(target=self._wait_th , args=[self.thread] , daemon=daemon)
|
|
448
|
+
signal.signal(signal.SIGINT, self.on_ctrl_c )
|
|
449
|
+
if block:
|
|
450
|
+
self.wait()
|
|
365
451
|
return self.thread
|
|
366
452
|
|
|
453
|
+
def wait(self):
|
|
454
|
+
"""
|
|
455
|
+
Block the main thread to keep it alive for signal handling (Ctrl+C).
|
|
456
|
+
This allows Ctrl+C to be properly detected. Call this after startUi()
|
|
457
|
+
if you want signal handling to work.
|
|
458
|
+
"""
|
|
459
|
+
try:
|
|
460
|
+
while self.flaskprocess.is_alive():
|
|
461
|
+
self.flaskprocess.join(timeout=0.1)
|
|
462
|
+
except KeyboardInterrupt:
|
|
463
|
+
if not hasattr(self, '_shutting_down'):
|
|
464
|
+
self._shutting_down = True
|
|
465
|
+
self.on_ctrl_c()
|
|
466
|
+
except SystemExit:
|
|
467
|
+
# Re-raise SystemExit to allow proper program termination
|
|
468
|
+
raise
|
|
469
|
+
|
|
367
470
|
def stopUi(self) :
|
|
471
|
+
if hasattr(self, 'wsgi_server') and self.wsgi_server:
|
|
472
|
+
try:
|
|
473
|
+
self.wsgi_server.shutdown()
|
|
474
|
+
except:
|
|
475
|
+
pass
|
|
368
476
|
kill_thread(self.thread)
|
|
369
|
-
return True
|
|
370
|
-
|
|
477
|
+
return True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|