lyrpy 0.0.2__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.
lyr/LUDecotators.py ADDED
@@ -0,0 +1,418 @@
1
+ """LUsys.py"""
2
+ # -*- coding: UTF-8 -*-
3
+ __annotations__ = """
4
+ =======================================================
5
+ Copyright (c) 2023-2024
6
+ Author:
7
+ Lisitsin Y.R.
8
+ Project:
9
+ LU_PY
10
+ Python (LU)
11
+ Module:
12
+ LUDecotators.py
13
+
14
+ =======================================================
15
+ """
16
+
17
+ #------------------------------------------
18
+ # БИБЛИОТЕКИ python
19
+ #------------------------------------------
20
+ import time
21
+ import logging
22
+ from functools import wraps
23
+ import smtplib
24
+ import traceback
25
+ from email.mime.text import MIMEText
26
+
27
+ #------------------------------------------
28
+ # БИБЛИОТЕКИ сторонние
29
+ #------------------------------------------
30
+
31
+ #------------------------------------------
32
+ # БИБЛИОТЕКИ LU
33
+ #------------------------------------------
34
+ import lyr.LULog as LULog
35
+
36
+ #---------------------------------------------------------------
37
+ # TIMING
38
+ #---------------------------------------------------------------
39
+ def TIMING(func):
40
+ #beginfunction
41
+
42
+ def wrapper(*args, **kwargs):
43
+ #beginfunction
44
+ start_time = time.time()
45
+ result = func(*args, **kwargs)
46
+ end_time = time.time()
47
+ s = f"Функция {func.__name__} работала {end_time - start_time} секунд..."
48
+ LULog.LoggerTOOLS_AddLevel (LULog.DEBUGTEXT, s)
49
+ return result
50
+ #endfunction
51
+
52
+ return wrapper
53
+ #endfunction
54
+
55
+ #---------------------------------------------------------------
56
+ # retry
57
+ #---------------------------------------------------------------
58
+ """
59
+ 1. Декоратор retry
60
+ В проектах по обработке данных и разработке программного обеспечения очень много случаев, когда мы зависим от внешних систем. Не всё всегда находятся под нашим контролем.
61
+ Иногда происходят неожиданное событие, во время которых нам бы хотелось, чтобы внешняя система сама исправляла возникнувшие ошибки и перезапускалась.
62
+ Я предпочитаю реализовывать эту логику с помощью декоратора retry, который позволяет повторно выполнять программу через N-ное количество времени.
63
+ """
64
+ def retry(max_tries=3, delay_seconds=1):
65
+ def decorator_retry(func):
66
+ @wraps(func)
67
+ def wrapper_retry(*args, **kwargs):
68
+ tries = 0
69
+ while tries < max_tries:
70
+ try:
71
+ return func(*args, **kwargs)
72
+ except Exception as e:
73
+ tries += 1
74
+ if tries == max_tries:
75
+ raise e
76
+ time.sleep(delay_seconds)
77
+ return wrapper_retry
78
+ return decorator_retry
79
+ @retry(max_tries=5, delay_seconds=2)
80
+ def call_dummy_api():
81
+ response = None
82
+ # response = requests.get("https://jsonplaceholder.typicode.com/todos/1")
83
+ return response
84
+ #endfunction
85
+
86
+ #---------------------------------------------------------------
87
+ # memoize
88
+ #---------------------------------------------------------------
89
+ """
90
+ 2. Результаты функции кэширования
91
+ Некоторые части нашего кода редко меняют своё поведение. Тем не менее, если такое всё-таки произойдёт, это может отнять большую часть наших вычислительных мощностей. В таких ситуациях мы можем использовать декоратор для кэширования вызовов функций
92
+ Функция будет запущена только один раз, если входные данные совпадают. При каждом последующем запуске результаты будут извлекаться из кэша. Следовательно, нам не нужно будет постоянно выполнять дорогостоящие вычисления.
93
+ """
94
+ def memoize(func):
95
+ cache = {}
96
+ def wrapper(*args):
97
+ if args in cache:
98
+ return cache[args]
99
+ else:
100
+ result = func(*args)
101
+ cache[args] = result
102
+ return result
103
+ return wrapper
104
+ #endfunction
105
+
106
+ #---------------------------------------------------------------
107
+ # email_on_failure
108
+ #---------------------------------------------------------------
109
+ """
110
+ 5. Декоратор Notification
111
+ Наконец, очень полезным декоратором в производственных системах является декоратор Notification.
112
+ Ещё раз, даже при нескольких повторных попытках хорошо протестированная кодовая база может потерпеть неудачу. И когда это произойдет, нам нужно сообщить кому-нибудь об этом, чтобы принять быстрые меры.
113
+ Это не ново, если вы когда-либо создавали конвейер данных и надеялись, что он всегда будет работать без перебоев.
114
+ Следующий декоратор отправляет электронное письмо всякий раз, когда выполнение внутренней функции завершается неудачей. В вашем случае это не обязательно должно быть уведомление по электронной почте. Вы можете настроить его для отправки уведомлений Teams / slack:
115
+ """
116
+ def email_on_failure (sender_email, password, recipient_email):
117
+ def decorator (func):
118
+ def wrapper (*args, **kwargs):
119
+ try:
120
+ return func (*args, **kwargs)
121
+ except Exception as e:
122
+ # format the error message and traceback
123
+ err_msg = f"Error: {str (e)}\n\nTraceback:\n{traceback.format_exc ()}"
124
+
125
+ # create the email message
126
+ message = MIMEText (err_msg)
127
+ message ['Subject'] = f"{func.__name__} failed"
128
+ message ['From'] = sender_email
129
+ message ['To'] = recipient_email
130
+
131
+ # send the email
132
+ with smtplib.SMTP_SSL ('smtp.gmail.com', 465) as smtp:
133
+ smtp.login (sender_email, password)
134
+ smtp.sendmail (sender_email, recipient_email, message.as_string ())
135
+
136
+ # re-raise the exception
137
+ raise
138
+
139
+ return wrapper
140
+ return decorator
141
+ #endfunction
142
+
143
+ #---------------------------------------------------------------
144
+ # my_function
145
+ #---------------------------------------------------------------
146
+ @email_on_failure (sender_email = 'your_email@gmail.com', password = 'your_password',
147
+ recipient_email = 'recipient_email@gmail.com')
148
+ def my_function ():
149
+ # code that might fail
150
+ ...
151
+ #endfunction
152
+
153
+ #---------------------------------------------------------------
154
+ # timeit
155
+ #---------------------------------------------------------------
156
+ """
157
+ To overcome this, created the @timeit decorator which allows you to measure the execution time of the method/function by just adding the @timeit decorator on the method.
158
+ @timeit decorator:
159
+ """
160
+ def timeit(method):
161
+ def timed(*args, **kw):
162
+ ts = time.time()
163
+ result = method(*args, **kw)
164
+ te = time.time()
165
+ if 'log_time' in kw:
166
+ name = kw.get('log_name', method.__name__.upper())
167
+ kw['log_time'][name] = int((te - ts) * 1000)
168
+ else:
169
+ print ('%r %2.2f ms' % (method.__name__, (te - ts) * 1000))
170
+ return result
171
+ return timed
172
+ #endfunction
173
+
174
+ #---------------------------------------------------------------
175
+ # get_all_employee_details
176
+ #---------------------------------------------------------------
177
+ # Adding decorator to the method
178
+ @timeit
179
+ def get_all_employee_details(**kwargs):
180
+ print ('employee details')
181
+ # The code will look like this after removing the redundant code.
182
+ # logtime_data = {}
183
+ # employees = Employee.get_all_employee_details(log_time=logtime_data)
184
+ # Hurray!! All that messy code is gone and now it looks simple and clear.
185
+ # log_time and log_name are optional. Make use of them accordingly when needed.
186
+
187
+ #---------------------------------------------------------------
188
+ # timeit
189
+ #---------------------------------------------------------------
190
+ def timeit(func):
191
+ @wraps(func)
192
+ def timeit_wrapper(*args, **kwargs):
193
+ start_time = time.perf_counter()
194
+ result = func(*args, **kwargs)
195
+ end_time = time.perf_counter()
196
+ total_time = end_time - start_time
197
+ print(f'Function {func.__name__}{args} {kwargs} Took {total_time:.4f} seconds')
198
+ return result
199
+ return timeit_wrapper
200
+ #endfunction
201
+
202
+ @timeit
203
+ def calculate_something(num):
204
+ """
205
+ Simple function that returns sum of all numbers up to the square of num.
206
+ """
207
+ total = sum((x for x in range(0, num**2)))
208
+ return total
209
+ #endfunction
210
+
211
+ class Calculator:
212
+ @timeit
213
+ def calculate_something(self, num):
214
+ """
215
+ an example function that returns sum of all numbers up to the square of num
216
+ """
217
+ total = sum((x for x in range(0, num**2)))
218
+ return total
219
+
220
+ def __repr__(self):
221
+ return f'calc_object:{id(self)}'
222
+
223
+ #---------------------------------------------------------------
224
+ #
225
+ #---------------------------------------------------------------
226
+ """
227
+ 01.Свойства @property
228
+ Декоратор @property облегчает создание свойств в классах Python. Свойства выглядят как обычные атрибуты (поля) класса, но при их чтении вызывается геттер (getter), при записи – сеттер (setter), а при удалении – делитер (deleter). Геттер и делитер опциональны.
229
+
230
+ 02.Статические и классовые методы
231
+ Методы могут быть не только у экземпляра класса, но и у самого класса, которые вызываются без какого-то экземпляра (без self). Декораторы @staticmethod и @classmethod как раз делают метод таким (статическим или классовым). Эти декораторы встроены и видны без import.
232
+
233
+ Статический метод – это способ поместить функцию в класс, если она логически относится к этому классу. Статический метод ничего не знает о классе, из которого его вызвали.
234
+
235
+ class Foo:
236
+ @staticmethod
237
+ def help():
238
+ print('help for Foo class')
239
+ Foo.help()
240
+
241
+ Классовый метод напротив знает, из какого класса его вызывают. Он принимает неявный первый аргумент (обычно его зовут cls), который содержит вызывающий класс. Классовые методы прекрасно подходят, когда нужно учесть иерархию наследования. Пример: метод group создает список из нескольких людей. Причем для Person – список Person, а для Worker – список Worker. Со @staticmethod такое бы не вышло:
242
+
243
+ class Person:
244
+ @classmethod
245
+ def group(cls, n):
246
+ # cls именно тот класс, который вызвал
247
+ return [cls() for _ in range(n)]
248
+ def __repr__(self):
249
+ return 'Person'
250
+ class Worker(Person):
251
+ def __repr__(self):
252
+ return 'Worker'
253
+ print(Person.group(3))
254
+ # [Person, Person, Person]
255
+ print(Worker.group(2))
256
+ # [Worker, Worker]
257
+
258
+ 03.@contextmanager
259
+ Этот декоратор позволяет получить из генератора – контекст менеджер. Находится в стандартном модуле contextlib. Пример открытие файла.
260
+
261
+ from contextlib import contextmanager
262
+ @contextmanager
263
+ def my_open(name, mode='r'):
264
+ # тут код для получения ресурса
265
+ f = open(name, mode)
266
+ print('Файл открыт:', name)
267
+ try:
268
+ yield f
269
+ finally:
270
+ # Code to release resource, e.g.:
271
+ f.close()
272
+ print('Файл закрыт:', name)
273
+ # использование
274
+ with my_open('1.txt', 'w') as f:
275
+ f.write('Hello')
276
+ f.fooooo() # <- error
277
+ # Файл открыт: 1.txt
278
+ # Traceback (most recent call last):
279
+ # Файл закрыт: 1.txt
280
+
281
+ В этом генераторе есть единственный yield – он возвращает как раз нужный ресурс. Все, что до него – код захвата ресурса (будет выполнен в методе __enter__), например, открытие файла. Мы оборачиваем yield в try/finally, чтобы если даже в блоке кода with произойдет ошибка, то исключение выбросится из yield, но код закрытия файла в блоке finally будет выполнен в любом случае. Код закрытия выполняется в методе __exit__ менеджера контекста.
282
+
283
+ Асинхронная версия этого декоратора – @asynccontextmanager. Пример:
284
+
285
+ from contextlib import asynccontextmanager
286
+ @asynccontextmanager
287
+ async def get_connection():
288
+ conn = await acquire_db_connection()
289
+ try:
290
+ yield conn
291
+ finally:
292
+ await release_db_connection(conn)
293
+ # использование
294
+ async def get_all_users():
295
+ async with get_connection() as conn:
296
+ return conn.query('SELECT ...')
297
+
298
+ @functools.wraps
299
+ Декоратор @functools.wraps полезен при разработке других декораторов. Передает имя, документацию и прочую мета-информацию из декорируемой функции к ее обертке. Подробнее в статье про декораторы.
300
+
301
+ @atexit.register
302
+ Декоратор @atexit.register регистрирует функцию для вызова ее при завершении работы процесса Python.
303
+
304
+ import atexit
305
+ @atexit.register
306
+ def goodbye():
307
+ print("You are now leaving the Python sector.")
308
+ Измерение времени @timeit
309
+ Переходим к самописным декораторам.
310
+
311
+ Этот декоратор измеряет время выполнения функции, которую декорирует.
312
+
313
+ import time
314
+ from functools import wraps
315
+ def timeit(method):
316
+ @wraps(method)
317
+ def timed(*args, **kw):
318
+ ts = time.monotonic()
319
+ result = method(*args, **kw)
320
+ te = time.monotonic()
321
+ ms = (te - ts) * 1000
322
+ all_args = ', '.join(tuple(f'{a!r}' for a in args)
323
+ + tuple(f'{k}={v!r}' for k, v in kw.items()))
324
+ print(f'{method.__name__}({all_args}): {ms:2.2f} ms')
325
+ return result
326
+ return timed
327
+ # использование:
328
+ @timeit
329
+ def slow_func(x, y, sleep):
330
+ time.sleep(sleep)
331
+ return x + y
332
+ slow_func(10, 20, sleep=2)
333
+ # печатает: slow_func(10, 20, sleep=2): 2004.65 ms
334
+
335
+ Как видите, нам не нужно вмешиваться в код функции, не нужно каждый раз писать измеритель времени, декоратор отлично экономит нашу работу: надо измерить время – дописали @timeit и видим все, как на ладони.
336
+
337
+ Повторитель
338
+ Повторяет вызов функции n раз, возвращает последний результат.
339
+
340
+ from functools import wraps
341
+ def repeat(_func=None, *, num_times=2):
342
+ def decorator_repeat(func):
343
+ @wraps(func)
344
+ def wrapper_repeat(*args, **kwargs):
345
+ value = None
346
+ for _ in range(num_times):
347
+ value = func(*args, **kwargs)
348
+ return value
349
+ return wrapper_repeat
350
+ if _func is None:
351
+ return decorator_repeat
352
+ else:
353
+ return decorator_repeat(_func)
354
+ @repeat(num_times=5)
355
+ def foo():
356
+ print('текст')
357
+ """
358
+
359
+ """
360
+ Замедлитель
361
+ Замедляет исполнение функции на нужное число секунд. Бывает полезно для отладки.
362
+
363
+ from functools import wraps
364
+ import time
365
+ def slow_down(seconds=1):
366
+ def _slow_down(func):
367
+ # Sleep 1 second before calling the function
368
+ @wraps(func)
369
+ def wrapper_slow_down(*args, **kwargs):
370
+ time.sleep(seconds)
371
+ return func(*args, **kwargs)
372
+ return wrapper_slow_down
373
+ return _slow_down
374
+ @slow_down(seconds=0.5)
375
+ def foo():
376
+ print('foo')
377
+ def bar():
378
+ foo() # каждый foo по полсекунды
379
+ foo()
380
+
381
+ Помощник для отладки
382
+ Этот декоратор будет логгировать все вызовы функции и печатать ее аргументы и возвращаемое значение.
383
+
384
+ from functools import wraps
385
+ # Печатает сигнатуру вызова и возвращаемое значение
386
+ import functools
387
+ def debug(func):
388
+ @wraps(func)
389
+ def wrapper_debug(*args, **kwargs):
390
+ args_repr = [repr(a) for a in args]
391
+ kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
392
+ signature = ", ".join(args_repr + kwargs_repr)
393
+ print(f"Calling {func.__name__}({signature})")
394
+ value = func(*args, **kwargs)
395
+ print(f"{func.__name__!r} returned {value!r}")
396
+ return value
397
+ return wrapper_debug
398
+ @debug
399
+ def testee(x, y):
400
+ print(x + y)
401
+ """
402
+
403
+
404
+ #------------------------------------------
405
+ def main ():
406
+ #beginfunction
407
+ print('main LUDecotators.py ...')
408
+ #endfunction
409
+
410
+ #------------------------------------------
411
+ #
412
+ #------------------------------------------
413
+ #beginmodule
414
+ if __name__ == "__main__":
415
+ main()
416
+ #endif
417
+
418
+ #endmodule
lyr/LUDict.py ADDED
@@ -0,0 +1,117 @@
1
+ """LUDict.py"""
2
+ # -*- coding: UTF-8 -*-
3
+ __annotations__ = """
4
+ =======================================================
5
+ Copyright (c) 2023-2024
6
+ Author:
7
+ Lisitsin Y.R.
8
+ Project:
9
+ LU_PY
10
+ Python (LU)
11
+ Module:
12
+ LUDict.py
13
+
14
+ =======================================================
15
+ """
16
+
17
+ #------------------------------------------
18
+ # БИБЛИОТЕКИ python
19
+ #------------------------------------------
20
+ import sys
21
+ import logging
22
+
23
+ #------------------------------------------
24
+ # БИБЛИОТЕКИ сторонние
25
+ #------------------------------------------
26
+ import json
27
+
28
+ #------------------------------------------
29
+ # БИБЛИОТЕКИ LU
30
+ #------------------------------------------
31
+
32
+ """
33
+ data = {}
34
+ data ['people'] = []
35
+ data ['people'].append ({
36
+ 'name': 'Scott',
37
+ 'website': 'pythonist.ru',
38
+ 'from': 'Nebraska'
39
+ })
40
+ data ['people'].append ({
41
+ 'name': 'Larry',
42
+ 'website': 'pythonist.ru',
43
+ 'from': 'Michigan'
44
+ })
45
+ data ['people'].append ({
46
+ 'name': 'Tim',
47
+ 'website': 'pythonist.ru',
48
+ 'from': 'Alabama'
49
+ })
50
+ """
51
+
52
+ #---------------------------------------------------------------
53
+ # PrintDict
54
+ #---------------------------------------------------------------
55
+ def PrintDict (ADict: dict):
56
+ """PrintDict"""
57
+ #beginfunction
58
+
59
+ # LResult = AARGS.get (AARGName).get (AARGValue)
60
+
61
+ for key, value in ADict.items():
62
+ # print(key, value)
63
+ try:
64
+ print("key:\t" + key)
65
+ # ADict (value)
66
+ except AttributeError:
67
+ print("value:\t" + str(value))
68
+ #endtry
69
+ #endfor
70
+ #endfunction
71
+
72
+ #---------------------------------------------------------------
73
+ # SaveDictJSON
74
+ #---------------------------------------------------------------
75
+ def SaveDictJSON (ADict, AFileName):
76
+ """SaveDictJSON"""
77
+ #beginfunction
78
+ with open (AFileName, 'w') as LFileDictJSON:
79
+ json.dump (ADict, LFileDictJSON)
80
+ #endwith
81
+ #endfunction
82
+
83
+ #---------------------------------------------------------------
84
+ # SaveDictSTR
85
+ #---------------------------------------------------------------
86
+ def SaveDictSTR (ADict, AFileName):
87
+ """SaveDictSTR"""
88
+ #beginfunction
89
+ LDict = json.dumps (ADict, ensure_ascii = False, indent = 4)
90
+ # Save a reference to the original standard output
91
+ original_stdout = sys.stdout
92
+ with open (AFileName, 'w') as LFileDictSTR:
93
+ # Change the standard output to the file we created.
94
+ sys.stdout = LFileDictSTR
95
+ print (LDict)
96
+ #endwith
97
+ # Reset the standard output to its original value
98
+ sys.stdout = original_stdout
99
+ #endfunction
100
+
101
+ #---------------------------------------------------------------
102
+ #
103
+ #---------------------------------------------------------------
104
+ def main ():
105
+ #beginfunction
106
+ print('main LUDict.py...')
107
+ #endfunction
108
+
109
+ #------------------------------------------
110
+ #
111
+ #------------------------------------------
112
+ #beginmodule
113
+ if __name__ == "__main__":
114
+ main()
115
+ #endif
116
+
117
+ #endmodule
lyr/LUDoc.py ADDED
@@ -0,0 +1,61 @@
1
+ """LUDoc.py"""
2
+ # -*- coding: UTF-8 -*-
3
+ __annotations__ = """
4
+ ------------------------------------------------------
5
+ Copyright (c) 2023-2024
6
+ Author:
7
+ Lisitsin Y.R.
8
+ Project:
9
+ LU_PY
10
+ Python (LU)
11
+ Module:
12
+ LUDoc.py
13
+
14
+ ------------------------------------------------------
15
+ """
16
+
17
+ #------------------------------------------
18
+ # БИБЛИОТЕКИ python
19
+ #------------------------------------------
20
+ import sys
21
+
22
+ #------------------------------------------
23
+ # БИБЛИОТЕКИ сторонние
24
+ #------------------------------------------
25
+
26
+ #------------------------------------------
27
+ # БИБЛИОТЕКИ LU
28
+ #------------------------------------------
29
+ import lyr.LULog as LULog
30
+
31
+ #---------------------------------------------------------------
32
+ # PrintInfoObject
33
+ #---------------------------------------------------------------
34
+ def PrintInfoObject (AObject):
35
+ #beginfunction
36
+ # print (sys._getframe (0).f_code.co_name, '...')
37
+ # print (inspect.currentframe().f_code.co_name, '...')
38
+ # print (inspect.stack () [0] [3], '...')
39
+ # print (traceback.extract_stack () [-1].name, '...')
40
+ s = f'{AObject}'
41
+ #LULog_LoggerTOOLS_log(LULog.DEBUGTEXT, s)
42
+ LULog.LoggerTOOLS_AddLevel(LULog.DEBUGTEXT, s)
43
+ #endfunction
44
+
45
+ #---------------------------------------------------------------
46
+ # main
47
+ #---------------------------------------------------------------
48
+ def main ():
49
+ #beginfunction
50
+ print('main LUDoc.py...')
51
+ #endfunction
52
+
53
+ #------------------------------------------
54
+ #
55
+ #------------------------------------------
56
+ #beginmodule
57
+ if __name__ == "__main__":
58
+ main()
59
+ #endif
60
+
61
+ #endmodule
lyr/LUErrors.py ADDED
@@ -0,0 +1,80 @@
1
+ """LUErrors.py"""
2
+ # -*- coding: UTF-8 -*-
3
+ __annotations__ = """
4
+ =======================================================
5
+ Copyright (c) 2023-2024
6
+ Author:
7
+ Lisitsin Y.R.
8
+ Project:
9
+ LU_PY
10
+ Python (LU)
11
+ Module:
12
+ LUErrors.py
13
+
14
+ =======================================================
15
+ """
16
+
17
+ #------------------------------------------
18
+ # БИБЛИОТЕКИ python
19
+ #------------------------------------------
20
+ import logging
21
+
22
+ #------------------------------------------
23
+ # БИБЛИОТЕКИ сторонние
24
+ #------------------------------------------
25
+
26
+ #---------------------------------------------------------------
27
+ # class LUFileError_FileERROR
28
+ #---------------------------------------------------------------
29
+ class LUFileError_FileERROR(Exception):
30
+ """Ошибки в модуле LUFile при обработке файла"""
31
+ """
32
+ Атрибуты:
33
+ AFileName:
34
+ AMessage: объяснение ошибки
35
+ """
36
+
37
+ def __init__(self, AFileName: str, AMessage="Файл не существует."):
38
+ self.__FFileName: str = AFileName
39
+ self.__FMessage: str = AMessage
40
+ super().__init__(self.__FMessage)
41
+
42
+ # переопределяем метод '__str__'
43
+ def __str__(self):
44
+ return f'{self.__FFileName} -> {self.__FMessage}'
45
+
46
+ #--------------------------------------------------
47
+ # @property Message
48
+ #--------------------------------------------------
49
+ # getter
50
+ @property
51
+ def Message(self):
52
+ #beginfunction
53
+ return self.__FMessage
54
+ #endfunction
55
+ @Message.setter
56
+ def Message(self, Value: str):
57
+ #beginfunction
58
+ self.__FMessage: str = Value
59
+ #endfunction
60
+
61
+ #endclass
62
+
63
+ #------------------------------------------
64
+ # main
65
+ #------------------------------------------
66
+ def main ():
67
+ #beginfunction
68
+ print('main LUEditors.py ...')
69
+ #endfunction
70
+
71
+ #------------------------------------------
72
+ #
73
+ #------------------------------------------
74
+ #beginmodule
75
+ if __name__ == "__main__":
76
+ main()
77
+ #endif
78
+
79
+ #endmodule
80
+