reykit 1.0.0__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.
reykit/rtype.py ADDED
@@ -0,0 +1,106 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2025-06-13 02:05:46
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Type methods.
9
+ """
10
+
11
+
12
+ from typing import Any, Optional, Self
13
+ from collections.abc import Callable
14
+
15
+
16
+ __all__ = (
17
+ 'RStaticMeta',
18
+ 'RConfigMeta',
19
+ 'RNull',
20
+ 'RSingleton'
21
+ )
22
+
23
+
24
+ class RStaticMeta(type):
25
+ """
26
+ Rey's `static meta` type.
27
+ """
28
+
29
+
30
+ def __call__(cls):
31
+ """
32
+ Call method.
33
+ """
34
+
35
+ # Throw exception.
36
+ raise TypeError('static class, no instances allowed.')
37
+
38
+
39
+ class RConfigMeta(RStaticMeta):
40
+ """
41
+ Rey's `config meta` type.
42
+ """
43
+
44
+
45
+ def __getitem__(cls, name: str):
46
+ """
47
+ Get item.
48
+
49
+ Parameters
50
+ ----------
51
+ name : Item name.
52
+
53
+ Returns
54
+ -------
55
+ Item value.
56
+ """
57
+
58
+ # Get.
59
+ item = getattr(cls, name)
60
+
61
+ return item
62
+
63
+
64
+ def __setitem__(cls, name: str, value: Any) -> None:
65
+ """
66
+ Set item.
67
+
68
+ Parameters
69
+ ----------
70
+ name : Item name.
71
+ """
72
+
73
+ # Set.
74
+ setattr(cls, name, value)
75
+
76
+
77
+ class RNull(object, metaclass=RStaticMeta):
78
+ """
79
+ Rey's `null` type.
80
+ """
81
+
82
+
83
+ class RSingleton(object):
84
+ """
85
+ Rey's `singleton` type.
86
+ When instantiated, method `__singleton__` will be called only once, and will accept arguments.
87
+ """
88
+
89
+ _instance: Optional[Self] = None
90
+
91
+
92
+ def __new__(self, *arg: Any, **kwargs: Any) -> Self:
93
+ """
94
+ Build `singleton` instance.
95
+ """
96
+
97
+ # Build.
98
+ if self._instance is None:
99
+ self._instance = super().__new__(self)
100
+
101
+ ## Singleton method.
102
+ if hasattr(self, "__singleton__"):
103
+ __singleton__: Callable = getattr(self, "__singleton__")
104
+ __singleton__(self, *arg, **kwargs)
105
+
106
+ return self._instance
reykit/rwrap.py ADDED
@@ -0,0 +1,613 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-05 14:12:25
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Decorator methods.
9
+ """
10
+
11
+
12
+ from typing import Any, Optional, Union, Literal, overload
13
+ from collections.abc import Callable
14
+ from io import IOBase, StringIO
15
+ from inspect import getdoc
16
+ from functools import wraps as functools_wraps
17
+ from threading import Thread
18
+ from argparse import ArgumentParser
19
+ from contextlib import redirect_stdout
20
+
21
+ from .rexception import catch_exc
22
+ from .rstdout import echo
23
+ from .rsystem import get_arg_info
24
+ from .rtime import now, time_to, RTimeMark
25
+
26
+
27
+ __all__ = (
28
+ 'wrap_frame',
29
+ 'wrap_runtime',
30
+ 'wrap_thread',
31
+ 'wrap_exc',
32
+ 'wrap_retry',
33
+ 'wrap_dos_command',
34
+ 'wrap_cache_data',
35
+ 'wrap_cache',
36
+ 'wrap_redirect_stdout'
37
+ )
38
+
39
+
40
+ def wrap_frame(decorator: Callable) -> Callable:
41
+ """
42
+ Decorative frame.
43
+
44
+ Parameters
45
+ ----------
46
+ decorator : Decorator function.
47
+
48
+ Retuens
49
+ -------
50
+ Decorated decorator.
51
+
52
+ Examples
53
+ --------
54
+ Decoration function method one.
55
+ >>> @wrap_func
56
+ >>> def func(): ...
57
+ >>> result = func(param_a, param_b, param_c=1, param_d=2)
58
+
59
+ Decoration function method two.
60
+ >>> def func(): ...
61
+ >>> result = wrap_func(func, param_a, param_b, param_c=1, param_d=2)
62
+
63
+ Decoration function method three.
64
+ >>> def func(): ...
65
+ >>> result = wrap_func(func, _execute=True)
66
+
67
+ Decoration function method four.
68
+ >>> def func(): ...
69
+ >>> func = wrap_func(func)
70
+ >>> result = func(param_a, param_b, param_c=1, param_d=2)
71
+
72
+ Decoration function method five.
73
+ >>> def func(): ...
74
+ >>> func = wrap_func(func, param_a, param_c=1, _execute=False)
75
+ >>> result = func(param_b, param_d=2)
76
+ """
77
+
78
+
79
+ # Decorate Decorator.
80
+ @overload
81
+ def wrap(func: Callable, *args: Any, _execute: None = None, **kwargs: Any) -> Union[Callable, Any]: ...
82
+
83
+ @overload
84
+ def wrap(func: Callable, *args: Any, _execute: Literal[True] = None, **kwargs: Any) -> Any: ...
85
+
86
+ @overload
87
+ def wrap(func: Callable, *args: Any, _execute: Literal[False] = None, **kwargs: Any) -> Callable: ...
88
+
89
+ @functools_wraps(decorator)
90
+ def wrap(func: Callable, *args: Any, _execute: Optional[bool] = None, **kwargs: Any) -> Union[Callable, Any]:
91
+ """
92
+ Decorative shell.
93
+
94
+ Parameters
95
+ ----------
96
+ func : Function.
97
+ args : Position arguments of function.
98
+ _execute : Whether execute function, otherwise decorate function.
99
+ - `None`, When parameter `args` or `kwargs`: have values, then True, otherwise False.
100
+ - `bool`: Use this value.
101
+ kwargs : Keyword arguments of function.
102
+
103
+ Returns
104
+ -------
105
+ Decorated function or function return.
106
+ """
107
+
108
+ # Handle parameter.
109
+ if _execute is None:
110
+ if args != () or kwargs != {}:
111
+ _execute = True
112
+ else:
113
+ _execute = False
114
+
115
+ # Direct execution.
116
+ if _execute:
117
+ result = decorator(func, *args, **kwargs)
118
+ return result
119
+
120
+
121
+ # Decorate function.
122
+ @functools_wraps(func)
123
+ def wrap_sub(*_args: Any, **_kwargs: Any) -> Any:
124
+ """
125
+ Decorative sub shell.
126
+
127
+ Parameters
128
+ ----------
129
+ args : Position arguments of function.
130
+ kwargs : Keyword arguments of function.
131
+
132
+ Returns
133
+ -------
134
+ Function return.
135
+ """
136
+
137
+ # Decorate function.
138
+ result = decorator(func, *args, *_args, **kwargs, **_kwargs)
139
+
140
+ return result
141
+
142
+
143
+ return wrap_sub
144
+
145
+
146
+ return wrap
147
+
148
+
149
+ @overload
150
+ def wrap_runtime(
151
+ func: Callable,
152
+ *args: Any,
153
+ _return_report: Literal[False] = False,
154
+ **kwargs: Any
155
+ ) -> Any: ...
156
+
157
+ @overload
158
+ def wrap_runtime(func: Callable,
159
+ *args: Any,
160
+ _return_report: Literal[True] = False,
161
+ **kwargs: Any
162
+ ) -> tuple[Any, str]: ...
163
+
164
+ @wrap_frame
165
+ def wrap_runtime(
166
+ func: Callable,
167
+ *args: Any,
168
+ _return_report: bool = False,
169
+ **kwargs: Any
170
+ ) -> Union[Any, tuple[Any, str]]:
171
+ """
172
+ Decorator, print or return runtime report of the function.
173
+
174
+ Parameters
175
+ ----------
176
+ func : Function to be decorated.
177
+ args : Position arguments of decorated function.
178
+ _return_report : Whether return report, otherwise print report.
179
+ kwargs : Keyword arguments of decorated function.
180
+
181
+ Returns
182
+ -------
183
+ Function execution result and runtime report.
184
+ """
185
+
186
+ # Execute function and marking time.
187
+ rtm = RTimeMark()
188
+ rtm()
189
+ result = func(*args, **kwargs)
190
+ rtm()
191
+
192
+ # Generate report.
193
+ start_time = rtm.record[0]['datetime']
194
+ spend_time = rtm.record[1]['timedelta']
195
+ end_time = rtm.record[1]['datetime']
196
+ start_str = time_to(start_time, True)[:-3]
197
+ spend_str = time_to(spend_time, True)[:-3]
198
+ end_str = time_to(end_time, True)[:-3]
199
+ report = 'Start: %s -> Spend: %ss -> End: %s' % (
200
+ start_str,
201
+ spend_str,
202
+ end_str
203
+ )
204
+ title = func.__name__
205
+
206
+ # Return report.
207
+ if _return_report:
208
+ return result, report
209
+
210
+ # Print report.
211
+ echo(report, title=title)
212
+
213
+ return result
214
+
215
+
216
+ @overload
217
+ def wrap_thread(
218
+ func: Callable,
219
+ *args: Any,
220
+ _daemon: bool = True,
221
+ **kwargs: Any
222
+ ) -> Thread: ...
223
+
224
+ @wrap_frame
225
+ def wrap_thread(
226
+ func: Callable,
227
+ *args: Any,
228
+ _daemon: bool = True,
229
+ **kwargs: Any
230
+ ) -> Thread:
231
+ """
232
+ Decorator, function start in thread.
233
+
234
+ Parameters
235
+ ----------
236
+ func : Function to be decorated.
237
+ args : Position arguments of decorated function.
238
+ _daemon : Whether it is a daemon thread.
239
+ kwargs : Keyword arguments of decorated function.
240
+
241
+ Returns
242
+ -------
243
+ Thread object.
244
+ """
245
+
246
+ # Handle parameter.
247
+ thread_name = '%s_%d' % (func.__name__, now('timestamp'))
248
+
249
+ # Create thread.
250
+ thread = Thread(target=func, name=thread_name, args=args, kwargs=kwargs)
251
+ thread.daemon = _daemon
252
+
253
+ # Start thread.
254
+ thread.start()
255
+
256
+ return thread
257
+
258
+
259
+ @overload
260
+ def wrap_exc(
261
+ func: Callable,
262
+ *args: Any,
263
+ _exception: Union[BaseException, tuple[BaseException, ...]] = BaseException,
264
+ _handler: Optional[Callable] = None,
265
+ **kwargs: Any
266
+ ) -> Optional[Any]: ...
267
+
268
+ @wrap_frame
269
+ def wrap_exc(
270
+ func: Callable,
271
+ *args: Any,
272
+ _exception: Union[BaseException, tuple[BaseException, ...]] = BaseException,
273
+ _handler: Optional[Callable] = None,
274
+ **kwargs: Any
275
+ ) -> Optional[Any]:
276
+ """
277
+ Decorator, execute function with `try` and `except` syntax.
278
+
279
+ Parameters
280
+ ----------
281
+ func : Function to be decorated.
282
+ args : Position arguments of decorated function.
283
+ _exception : Catch exception types.
284
+ _handler : Exception handler, will return value.
285
+ kwargs : Keyword arguments of decorated function.
286
+
287
+ Returns
288
+ -------
289
+ Execution result of function or exception handle method.
290
+ """
291
+
292
+ # Execute function.
293
+ try:
294
+ result = func(*args, **kwargs)
295
+
296
+ # Handle exception.
297
+ except _exception:
298
+ if _handler is not None:
299
+ result = _handler()
300
+ else:
301
+ result = None
302
+
303
+ return result
304
+
305
+
306
+ @overload
307
+ def wrap_retry(
308
+ func: Callable,
309
+ *args: Any,
310
+ _report: Optional[str] = None,
311
+ _exception: Union[BaseException, tuple[BaseException, ...]] = BaseException,
312
+ _try_total: int = 1,
313
+ _try_count: int = 0,
314
+ **kwargs: Any
315
+ ) -> Any: ...
316
+
317
+ @wrap_frame
318
+ def wrap_retry(
319
+ func: Callable,
320
+ *args: Any,
321
+ _report: Optional[str] = None,
322
+ _exception: Union[BaseException, tuple[BaseException, ...]] = BaseException,
323
+ _try_total: int = 1,
324
+ _try_count: int = 0,
325
+ **kwargs: Any
326
+ ) -> Any:
327
+ """
328
+ Decorator, try again.
329
+
330
+ Parameters
331
+ ----------
332
+ func : Function to be decorated.
333
+ args : Position arguments of decorated function.
334
+ _report : Print report title.
335
+ - `None`: Not print.
336
+ - `str`: Print and use this title.
337
+ _exception : Catch exception types.
338
+ _try_total : Retry total.
339
+ _try_count : Retry count.
340
+ kwargs : Keyword arguments of decorated function.
341
+
342
+ Returns
343
+ -------
344
+ Function execution result.
345
+ """
346
+
347
+ # Try count not full.
348
+ if _try_count < _try_total:
349
+
350
+ ## Try.
351
+ try:
352
+ result = func(*args, **kwargs)
353
+ except _exception:
354
+
355
+ ## Report.
356
+ if _report is not None:
357
+ exc_report, *_ = catch_exc()
358
+ echo(
359
+ exc_report,
360
+ 'Retrying...',
361
+ title=_report,
362
+ frame='half'
363
+ )
364
+
365
+ ### Retry.
366
+ _try_count += 1
367
+ result = wrap_retry(
368
+ func,
369
+ *args,
370
+ _report=_report,
371
+ _exception=_exception,
372
+ _try_total=_try_total,
373
+ _try_count=_try_count,
374
+ **kwargs
375
+ )
376
+
377
+ # Try count full.
378
+ else:
379
+ result = func(*args, **kwargs)
380
+
381
+ return result
382
+
383
+
384
+ @overload
385
+ def wrap_dos_command(
386
+ func: Callable,
387
+ *args: Any,
388
+ **kwargs: Any
389
+ ) -> Any: ...
390
+
391
+ @wrap_frame
392
+ def wrap_dos_command(
393
+ func: Callable,
394
+ *args: Any,
395
+ **kwargs: Any
396
+ ) -> Any:
397
+ """
398
+ Decorator, use DOS command to input arguments to function.
399
+ Use DOS command `python file --help` to view help information.
400
+
401
+ Parameters
402
+ ----------
403
+ func : Function to be decorated.
404
+ args : Position arguments of decorated function.
405
+ kwargs : Keyword arguments of decorated function.
406
+
407
+ Returns
408
+ -------
409
+ Function execution result.
410
+ """
411
+
412
+ # Get parameter.
413
+ arg_info = get_arg_info(func)
414
+
415
+ # Set DOS command.
416
+ usage = getdoc(func)
417
+ if usage is not None:
418
+ usage = 'input arguments to function "%s"\n\n%s' % (func.__name__, usage)
419
+ parser = ArgumentParser(usage=usage)
420
+ for info in arg_info:
421
+ annotation_text = str(info['annotation'])
422
+ if info['annotation'] is None:
423
+ arg_type = str
424
+ arg_help = None
425
+ else:
426
+ if 'str' in annotation_text:
427
+ arg_type = str
428
+ elif 'float' in annotation_text:
429
+ arg_type = float
430
+ elif 'int' in annotation_text:
431
+ arg_type = int
432
+ elif 'bool' in annotation_text:
433
+ arg_type = bool
434
+ else:
435
+ arg_type = str
436
+ arg_help = annotation_text
437
+ if info['type'] in ('var_position', 'var_position'):
438
+ parser.add_argument(
439
+ info['name'],
440
+ nargs='*',
441
+ type=arg_type,
442
+ help=arg_help
443
+ )
444
+ else:
445
+ parser.add_argument(
446
+ info['name'],
447
+ nargs='?',
448
+ type=arg_type,
449
+ help=arg_help
450
+ )
451
+ kw_name = '--' + info['name']
452
+ parser.add_argument(
453
+ kw_name,
454
+ nargs='*',
455
+ type=arg_type,
456
+ help=arg_help,
457
+ metavar='value',
458
+ dest=kw_name
459
+ )
460
+
461
+ # Get argument.
462
+ namespace = parser.parse_args()
463
+ command_args = []
464
+ command_kwargs = {}
465
+ for info in arg_info:
466
+
467
+ ## Position argument.
468
+ value = getattr(namespace, info['name'])
469
+ if value is not None:
470
+ if value.__class__ == list:
471
+ command_args.extend(value)
472
+ else:
473
+ command_args.append(value)
474
+
475
+ ## Keyword argument.
476
+ if info['type'] not in ('var_position', 'var_position'):
477
+ kw_name = '--' + info['name']
478
+ kw_value = getattr(namespace, kw_name)
479
+ if kw_value.__class__ == list:
480
+ kw_value_len = len(kw_value)
481
+ match kw_value_len:
482
+ case 0:
483
+ kw_value = None
484
+ case 1:
485
+ kw_value = kw_value[0]
486
+ command_kwargs[info['name']] = kw_value
487
+
488
+ # Execute function.
489
+ if command_args == []:
490
+ func_args = args
491
+ else:
492
+ func_args = command_args
493
+ func_kwargs = {
494
+ **kwargs,
495
+ **command_kwargs
496
+ }
497
+ result = func(
498
+ *func_args,
499
+ **func_kwargs
500
+ )
501
+
502
+ return result
503
+
504
+
505
+ # Cache decorator data.
506
+ wrap_cache_data: dict[Callable, list[tuple[Any, Any, Any]]] = {}
507
+
508
+
509
+ @overload
510
+ def wrap_cache(
511
+ func: Callable,
512
+ *args: Any,
513
+ _overwrite: bool = False,
514
+ **kwargs: Any
515
+ ) -> Any: ...
516
+
517
+ @wrap_frame
518
+ def wrap_cache(
519
+ func: Callable,
520
+ *args: Any,
521
+ _overwrite: bool = False,
522
+ **kwargs: Any
523
+ ) -> Any:
524
+ """
525
+ Decorator, Cache the return result of function input.
526
+ if no cache, cache it.
527
+ if cached, skip execution and return result.
528
+
529
+ Parameters
530
+ ----------
531
+ func : Function to be decorated.
532
+ args : Position arguments of decorated function.
533
+ _overwrite : Whether to overwrite cache.
534
+ kwargs : Keyword arguments of decorated function.
535
+
536
+ Returns
537
+ -------
538
+ Function execution result.
539
+ """
540
+
541
+ # Index.
542
+ wrap_cache_data_func = wrap_cache_data.setdefault(func, [])
543
+ cache_index = None
544
+ for index, (cache_args, cache_kwargs, cache_result) in enumerate(wrap_cache_data_func):
545
+ if (
546
+ cache_args == args
547
+ and cache_kwargs == kwargs
548
+ ):
549
+ if _overwrite:
550
+ cache_index = index
551
+ break
552
+ else:
553
+ return cache_result
554
+
555
+ # Execute.
556
+ result = func(*args, **kwargs)
557
+
558
+ # Cache.
559
+ data = (args, kwargs, result)
560
+ if cache_index is None:
561
+ wrap_cache_data_func.append(data)
562
+ else:
563
+ wrap_cache_data_func[cache_index] = data
564
+
565
+ return result
566
+
567
+
568
+ @overload
569
+ def wrap_redirect_stdout(
570
+ func: Callable,
571
+ *args: Any,
572
+ _redirect: Optional[Union[list, IOBase]] = None,
573
+ **kwargs: Any
574
+ ) -> Any: ...
575
+
576
+ @wrap_frame
577
+ def wrap_redirect_stdout(
578
+ func: Callable,
579
+ *args: Any,
580
+ _redirect: Optional[Union[list, IOBase]] = None,
581
+ **kwargs: Any
582
+ ) -> Any:
583
+ """
584
+ Redirect standard output.
585
+
586
+ Parameters
587
+ ----------
588
+ func : Function to be decorated.
589
+ args : Position arguments of decorated function.
590
+ _redirect : Redirect output list or IO object.
591
+ kwargs : Keyword arguments of decorated function.
592
+
593
+ Returns
594
+ -------
595
+ Function execution result.
596
+ """
597
+
598
+ # Get parameter.
599
+ if isinstance(_redirect, IOBase):
600
+ str_io = _redirect
601
+ else:
602
+ str_io = StringIO()
603
+
604
+ # Execute.
605
+ with redirect_stdout(str_io):
606
+ result = func(*args, **kwargs)
607
+
608
+ # Save.
609
+ if _redirect.__class__ == list:
610
+ value = str_io.getvalue()
611
+ _redirect.append(value)
612
+
613
+ return result