reykit 1.1.26__py3-none-any.whl → 1.1.28__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/rbase.py ADDED
@@ -0,0 +1,973 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2024-07-17 09:46:40
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Base methods.
9
+ """
10
+
11
+
12
+ from typing import Any, Literal, Self, TypeVar, NoReturn, overload
13
+ from types import TracebackType
14
+ from collections.abc import Callable, Iterable, Mapping
15
+ from sys import exc_info as sys_exc_info
16
+ from os.path import exists as os_exists
17
+ from traceback import format_exc
18
+ from warnings import warn as warnings_warn
19
+ from traceback import format_stack, extract_stack
20
+ from atexit import register as atexit_register
21
+ from time import sleep as time_sleep
22
+ from inspect import signature as inspect_signature, _ParameterKind, _empty
23
+ from varname import VarnameRetrievingError, argname
24
+
25
+
26
+ __all__ = (
27
+ 'T',
28
+ 'U',
29
+ 'V',
30
+ 'KT',
31
+ 'VT',
32
+ 'Base',
33
+ 'StaticMeta',
34
+ 'ConfigMeta',
35
+ 'Singleton',
36
+ 'Null',
37
+ 'null',
38
+ 'BaseError',
39
+ 'Exit',
40
+ 'Error',
41
+ 'throw',
42
+ 'warn',
43
+ 'catch_exc',
44
+ 'check_least_one',
45
+ 'check_most_one',
46
+ 'check_file_found',
47
+ 'check_file_exist',
48
+ 'check_response_code',
49
+ 'is_class',
50
+ 'is_instance',
51
+ 'is_iterable',
52
+ 'is_table',
53
+ 'is_num_str',
54
+ 'get_first_notnone',
55
+ 'get_stack_text',
56
+ 'get_stack_param',
57
+ 'get_arg_info',
58
+ 'get_name',
59
+ 'block',
60
+ 'at_exit'
61
+ )
62
+
63
+
64
+ # Generic.
65
+ T = TypeVar('T') # Any.
66
+ U = TypeVar('U') # Any.
67
+ V = TypeVar('V') # Any.
68
+ KT = TypeVar('KT') # Any dictionary key.
69
+ VT = TypeVar('VT') # Any dictionary value.
70
+
71
+
72
+ class Base(object):
73
+ """
74
+ Base type.
75
+ """
76
+
77
+ def __getitem__(self, name: str) -> Any:
78
+ """
79
+ Get Attribute.
80
+
81
+ Parameters
82
+ ----------
83
+ name : Attribute name.
84
+
85
+ Returns
86
+ -------
87
+ Attribute value.
88
+ """
89
+
90
+ # Get.
91
+ value = getattr(self, name)
92
+
93
+ return value
94
+
95
+
96
+ def __setitem__(self, name: str, value: Any) -> None:
97
+ """
98
+ Set Attribute.
99
+
100
+ Parameters
101
+ ----------
102
+ name : Attribute name.
103
+ value : Attribute value.
104
+ """
105
+
106
+ # Set.
107
+ setattr(self, name, value)
108
+
109
+
110
+ def __delitem__(self, name: str) -> None:
111
+ """
112
+ Delete Attribute.
113
+
114
+ Parameters
115
+ ----------
116
+ name : Attribute name.
117
+ """
118
+
119
+ # Delete.
120
+ delattr(self, name)
121
+
122
+
123
+ class StaticMeta(Base, type):
124
+ """
125
+ Static meta type.
126
+ """
127
+
128
+
129
+ def __call__(cls):
130
+ """
131
+ Call method.
132
+ """
133
+
134
+ # Throw exception.
135
+ raise TypeError('static class, no instances allowed.')
136
+
137
+
138
+ class ConfigMeta(StaticMeta):
139
+ """
140
+ Config meta type.
141
+ """
142
+
143
+
144
+ def __getitem__(cls, name: str) -> Any:
145
+ """
146
+ Get item.
147
+
148
+ Parameters
149
+ ----------
150
+ name : Item name.
151
+
152
+ Returns
153
+ -------
154
+ Item value.
155
+ """
156
+
157
+ # Get.
158
+ item = getattr(cls, name)
159
+
160
+ return item
161
+
162
+
163
+ def __setitem__(cls, name: str, value: Any) -> None:
164
+ """
165
+ Set item.
166
+
167
+ Parameters
168
+ ----------
169
+ name : Item name.
170
+ """
171
+
172
+ # Set.
173
+ setattr(cls, name, value)
174
+
175
+
176
+ class Singleton(Base):
177
+ """
178
+ Singleton type.
179
+ When instantiated, method `__singleton__` will be called only once, and will accept arguments.
180
+
181
+ Attributes
182
+ ----------
183
+ _instance : Global singleton instance.
184
+ """
185
+
186
+ _instance: Self | None = None
187
+
188
+
189
+ def __new__(self, *arg: Any, **kwargs: Any) -> Self:
190
+ """
191
+ Build `singleton` instance.
192
+ """
193
+
194
+ # Build.
195
+ if self._instance is None:
196
+ self._instance = super().__new__(self)
197
+
198
+ ## Singleton method.
199
+ if hasattr(self, "__singleton__"):
200
+ __singleton__: Callable = getattr(self, "__singleton__")
201
+ __singleton__(self, *arg, **kwargs)
202
+
203
+ return self._instance
204
+
205
+
206
+ class Null(Singleton):
207
+ """
208
+ Null type.
209
+ """
210
+
211
+
212
+ null = Null()
213
+
214
+
215
+ class BaseError(Base, BaseException):
216
+ """
217
+ Base error type.
218
+ """
219
+
220
+
221
+ class Exit(BaseError):
222
+ """
223
+ Exit type.
224
+ """
225
+
226
+
227
+ class Error(BaseError):
228
+ """
229
+ Error type.
230
+ """
231
+
232
+
233
+ def throw(
234
+ exception: type[BaseException] = AssertionError,
235
+ value: Any = null,
236
+ *values: Any,
237
+ text: str | None = None,
238
+ frame: int = 2
239
+ ) -> NoReturn:
240
+ """
241
+ Throw exception.
242
+
243
+ Parameters
244
+ ----------
245
+ exception : Exception Type.
246
+ value : Exception value.
247
+ values : Exception values.
248
+ text : Exception text.
249
+ frame : Number of code to upper level.
250
+ """
251
+
252
+ # Text.
253
+ if text is None:
254
+ if exception.__doc__ is not None:
255
+ text = exception.__doc__.strip()
256
+ if (
257
+ text is None
258
+ or text == ''
259
+ ):
260
+ text = 'use error'
261
+ else:
262
+ text = text[0].lower() + text[1:]
263
+
264
+ ## Value.
265
+ if value != null:
266
+ values = (value,) + values
267
+
268
+ ### Name.
269
+ name = get_name(value, frame)
270
+ names = (name,)
271
+ if values != ():
272
+ names_values = get_name(values)
273
+ if names_values is not None:
274
+ names += names_values
275
+
276
+ ### Convert.
277
+ match exception:
278
+ case TypeError():
279
+ values = [
280
+ type(value)
281
+ for value in values
282
+ if value is not None
283
+ ]
284
+ case TimeoutError():
285
+ values = [
286
+ int(value)
287
+ if value % 1 == 0
288
+ else round(value, 3)
289
+ for value in values
290
+ if type(value) == float
291
+ ]
292
+ values = [
293
+ repr(value)
294
+ for value in values
295
+ ]
296
+
297
+ ### Join.
298
+ if names == ():
299
+ values_len = len(values)
300
+ text_value = ', '.join(values)
301
+ if values_len == 1:
302
+ text_value = 'value is ' + text_value
303
+ else:
304
+ text_value = 'values is (%s)' % text_value
305
+ else:
306
+ names_values = zip(names, values)
307
+ text_value = ', '.join(
308
+ [
309
+ 'parameter "%s" is %s' % (name, value)
310
+ for name, value in names_values
311
+ ]
312
+ )
313
+ text += ' %s.' % text_value
314
+
315
+ # Throw exception.
316
+ exception = exception(text)
317
+ raise exception
318
+
319
+
320
+ def warn(
321
+ *infos: Any,
322
+ exception: type[BaseException] = UserWarning,
323
+ stacklevel: int = 3
324
+ ) -> None:
325
+ """
326
+ Throw warning.
327
+
328
+ Parameters
329
+ ----------
330
+ infos : Warn informations.
331
+ exception : Exception type.
332
+ stacklevel : Warning code location, number of recursions up the code level.
333
+ """
334
+
335
+ # Handle parameter.
336
+ if infos == ():
337
+ infos = 'Warning!'
338
+ elif len(infos) == 1:
339
+ if type(infos[0]) == str:
340
+ infos = infos[0]
341
+ else:
342
+ infos = str(infos[0])
343
+ else:
344
+ infos = str(infos)
345
+
346
+ # Throw warning.
347
+ warnings_warn(infos, exception, stacklevel)
348
+
349
+
350
+ def catch_exc(
351
+ title: str | None = None
352
+ ) -> tuple[str, type[BaseException], BaseException, TracebackType]:
353
+ """
354
+ Catch exception information and print, must used in `except` syntax.
355
+
356
+ Parameters
357
+ ----------
358
+ title : Print title.
359
+ - `None`: Not print.
360
+ - `str`: Print and use this title.
361
+
362
+ Returns
363
+ -------
364
+ Exception data.
365
+ - `str`: Exception report text.
366
+ - `type[BaseException]`: Exception type.
367
+ - `BaseException`: Exception instance.
368
+ - `TracebackType`: Exception traceback instance.
369
+ """
370
+
371
+ # Get parameter.
372
+ exc_report = format_exc()
373
+ exc_report = exc_report.strip()
374
+ exc_type, exc_instance, exc_traceback = sys_exc_info()
375
+
376
+ # Print.
377
+ if title is not None:
378
+
379
+ ## Import.
380
+ from .rstdout import echo
381
+
382
+ ## Execute.
383
+ echo(exc_report, title=title, frame='half')
384
+
385
+ return exc_report, exc_type, exc_instance, exc_traceback
386
+
387
+
388
+ @overload
389
+ def check_least_one(*values: None) -> NoReturn: ...
390
+
391
+ @overload
392
+ def check_least_one(*values: Any) -> None: ...
393
+
394
+ def check_least_one(*values: Any) -> None:
395
+ """
396
+ Check that at least one of multiple values is not null, when check fail, then throw exception.
397
+
398
+ Parameters
399
+ ----------
400
+ values : Check values.
401
+ """
402
+
403
+ # Check.
404
+ for value in values:
405
+ if value is not None:
406
+ return
407
+
408
+ # Throw exception.
409
+ vars_name = get_name(values)
410
+ if vars_name is not None:
411
+ vars_name_de_dup = list(set(vars_name))
412
+ vars_name_de_dup.sort(key=vars_name.index)
413
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
414
+ else:
415
+ vars_name_str = ''
416
+ raise TypeError(f'at least one of parameters{vars_name_str} is not None')
417
+
418
+
419
+ def check_most_one(*values: Any) -> None:
420
+ """
421
+ Check that at most one of multiple values is not null, when check fail, then throw exception.
422
+
423
+ Parameters
424
+ ----------
425
+ values : Check values.
426
+ """
427
+
428
+ # Check.
429
+ exist = False
430
+ for value in values:
431
+ if value is not None:
432
+ if exist is True:
433
+
434
+ # Throw exception.
435
+ vars_name = get_name(values)
436
+ if vars_name is not None:
437
+ vars_name_de_dup = list(set(vars_name))
438
+ vars_name_de_dup.sort(key=vars_name.index)
439
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
440
+ else:
441
+ vars_name_str = ''
442
+ raise TypeError(f'at most one of parameters{vars_name_str} is not None')
443
+
444
+ exist = True
445
+
446
+
447
+ def check_file_found(path: str) -> None:
448
+ """
449
+ Check if file path found, if not, throw exception.
450
+
451
+ Parameters
452
+ ----------
453
+ path : File path.
454
+ """
455
+
456
+ # Check.
457
+ exist = os_exists(path)
458
+
459
+ # Throw exception.
460
+ if not exist:
461
+ throw(FileNotFoundError, path)
462
+
463
+
464
+ def check_file_exist(path: str) -> None:
465
+ """
466
+ Check if file path exist, if exist, throw exception.
467
+
468
+ Parameters
469
+ ----------
470
+ path : File path.
471
+ """
472
+
473
+ # Check.
474
+ exist = os_exists(path)
475
+
476
+ # Throw exception.
477
+ if exist:
478
+ throw(FileExistsError, path)
479
+
480
+
481
+ def check_response_code(
482
+ code: int,
483
+ range_: int | Iterable[int] | None = None
484
+ ) -> bool:
485
+ """
486
+ Check if the response code is in range.
487
+
488
+ Parameters
489
+ ----------
490
+ code : Response code.
491
+ range_ : Pass the code range.
492
+ - `None`: Check if is between 200 and 299.
493
+ - `int`: Check if is this value.
494
+ - `Iterable`: Check if is in sequence.
495
+
496
+ Returns
497
+ -------
498
+ Check result.
499
+ """
500
+
501
+ # Check.
502
+ match range_:
503
+ case None:
504
+ result = code // 100 == 2
505
+ case int():
506
+ result = code == range_
507
+ case _ if hasattr(range_, '__contains__'):
508
+ result = code in range_
509
+ case _:
510
+ throw(TypeError, range_)
511
+
512
+ # Throw exception.
513
+ if not result:
514
+ throw(value=code)
515
+
516
+ return result
517
+
518
+
519
+ def is_class(obj: Any) -> bool:
520
+ """
521
+ Judge whether it is class.
522
+
523
+ Parameters
524
+ ----------
525
+ obj : Judge object.
526
+
527
+ Returns
528
+ -------
529
+ Judgment result.
530
+ """
531
+
532
+ # Judge.
533
+ judge = isinstance(obj, type)
534
+
535
+ return judge
536
+
537
+
538
+ def is_instance(obj: Any) -> bool:
539
+ """
540
+ Judge whether it is instance.
541
+
542
+ Parameters
543
+ ----------
544
+ obj : Judge object.
545
+
546
+ Returns
547
+ -------
548
+ Judgment result.
549
+ """
550
+
551
+ # judge.
552
+ judge = not is_class(obj)
553
+
554
+ return judge
555
+
556
+
557
+ def is_iterable(
558
+ obj: Any,
559
+ exclude_types: Iterable[type] | None = None
560
+ ) -> bool:
561
+ """
562
+ Judge whether it is iterable.
563
+
564
+ Parameters
565
+ ----------
566
+ obj : Judge object.
567
+ exclude_types : Non iterative types.
568
+
569
+ Returns
570
+ -------
571
+ Judgment result.
572
+ """
573
+
574
+ # Judge.
575
+ if (
576
+ hasattr(obj, '__iter__')
577
+ and not (
578
+ exclude_types is not None
579
+ and type(obj) in exclude_types
580
+ )
581
+ ):
582
+ return True
583
+
584
+ return False
585
+
586
+
587
+ def is_table(
588
+ obj: Any,
589
+ check_fields: bool = True
590
+ ) -> bool:
591
+ """
592
+ Judge whether it is `list[dict]` table format and keys and keys sort of the dict are the same.
593
+
594
+ Parameters
595
+ ----------
596
+ obj : Judge object.
597
+ check_fields : Do you want to check the keys and keys sort of the dict are the same.
598
+
599
+ Returns
600
+ -------
601
+ Judgment result.
602
+ """
603
+
604
+ # Judge.
605
+ if type(obj) != list:
606
+ return False
607
+ for element in obj:
608
+ if type(element) != dict:
609
+ return False
610
+
611
+ ## Check fields of table.
612
+ if check_fields:
613
+ keys_strs = [
614
+ ':'.join([str(key) for key in element.keys()])
615
+ for element in obj
616
+ ]
617
+ keys_strs_only = set(keys_strs)
618
+ if len(keys_strs_only) != 1:
619
+ return False
620
+
621
+ return True
622
+
623
+
624
+ def is_num_str(
625
+ string: str
626
+ ) -> bool:
627
+ """
628
+ Judge whether it is number string.
629
+
630
+ Parameters
631
+ ----------
632
+ string : String.
633
+
634
+ Returns
635
+ -------
636
+ Judgment result.
637
+ """
638
+
639
+ # Judge.
640
+ try:
641
+ float(string)
642
+ except (ValueError, TypeError):
643
+ return False
644
+
645
+ return True
646
+
647
+
648
+ @overload
649
+ def get_first_notnone(*values: None, default: T) -> T: ...
650
+
651
+ @overload
652
+ def get_first_notnone(*values: None) -> NoReturn: ...
653
+
654
+ @overload
655
+ def get_first_notnone(*values: T) -> T: ...
656
+
657
+ def get_first_notnone(*values: T, default: U = null) -> T | U:
658
+ """
659
+ Get the first value that is not `None`.
660
+
661
+ Parameters
662
+ ----------
663
+ values : Check values.
664
+ default : When all are `None`, then return this is value, or throw exception.
665
+ - `Any`: Return this is value.
666
+ - `Literal['exception']`: Throw exception.
667
+
668
+ Returns
669
+ -------
670
+ Return first not `None` value, when all are `None`, then return default value.
671
+ """
672
+
673
+ # Get value.
674
+ for value in values:
675
+ if value is not None:
676
+ return value
677
+
678
+ # Throw exception.
679
+ if default == null:
680
+ vars_name = get_name(values)
681
+ if vars_name is not None:
682
+ vars_name_de_dup = list(set(vars_name))
683
+ vars_name_de_dup.sort(key=vars_name.index)
684
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
685
+ else:
686
+ vars_name_str = ''
687
+ text = f'at least one of parameters{vars_name_str} is not None'
688
+ throw(ValueError, text=text)
689
+
690
+ return default
691
+
692
+
693
+ def get_stack_text(format_: Literal['plain', 'full'] = 'plain', limit: int = 2) -> str:
694
+ """
695
+ Get code stack text.
696
+
697
+ Parameters
698
+ ----------
699
+ format_ : Stack text format.
700
+ - `Literal['plain']`: Floor stack position.
701
+ - `Literal['full']`: Full stack information.
702
+ limit : Stack limit level.
703
+
704
+ Returns
705
+ -------
706
+ Code stack text.
707
+ """
708
+
709
+ # Get.
710
+ match format_:
711
+
712
+ ## Plain.
713
+ case 'plain':
714
+ limit += 1
715
+ stacks = format_stack(limit=limit)
716
+
717
+ ### Check.
718
+ if len(stacks) != limit:
719
+ throw(value=limit)
720
+
721
+ ### Convert.
722
+ text = stacks[0]
723
+ index_end = text.find(', in ')
724
+ text = text[2:index_end]
725
+
726
+ ## Full.
727
+ case 'full':
728
+ stacks = format_stack()
729
+ index_limit = len(stacks) - limit
730
+ stacks = stacks[:index_limit]
731
+
732
+ ### Check.
733
+ if len(stacks) == 0:
734
+ throw(value=limit)
735
+
736
+ ### Convert.
737
+ stacks = [
738
+ stack[2:].replace('\n ', '\n', 1)
739
+ for stack in stacks
740
+ ]
741
+ text = ''.join(stacks)
742
+ text = text[:-1]
743
+
744
+ ## Throw exception.
745
+ case _:
746
+ throw(ValueError, format_)
747
+
748
+ return text
749
+
750
+
751
+ @overload
752
+ def get_stack_param(format_: Literal['floor'] = 'floor', limit: int = 2) -> dict: ...
753
+
754
+ @overload
755
+ def get_stack_param(format_: Literal['full'], limit: int = 2) -> list[dict]: ...
756
+
757
+ def get_stack_param(format_: Literal['floor', 'full'] = 'floor', limit: int = 2) -> dict | list[dict]:
758
+ """
759
+ Get code stack parameters.
760
+
761
+ Parameters
762
+ ----------
763
+ format_ : Stack parameters format.
764
+ - `Literal['floor']`: Floor stack parameters.
765
+ - `Literal['full']`: Full stack parameters.
766
+ limit : Stack limit level.
767
+
768
+ Returns
769
+ -------
770
+ Code stack parameters.
771
+ """
772
+
773
+ # Get.
774
+ stacks = extract_stack()
775
+ index_limit = len(stacks) - limit
776
+ stacks = stacks[:index_limit]
777
+
778
+ # Check.
779
+ if len(stacks) == 0:
780
+ throw(value=limit)
781
+
782
+ # Convert.
783
+ match format_:
784
+
785
+ ## Floor.
786
+ case 'floor':
787
+ stack = stacks[-1]
788
+ params = {
789
+ 'filename': stack.filename,
790
+ 'lineno': stack.lineno,
791
+ 'name': stack.name,
792
+ 'line': stack.line
793
+ }
794
+
795
+ ## Full.
796
+ case 'full':
797
+ params = [
798
+ {
799
+ 'filename': stack.filename,
800
+ 'lineno': stack.lineno,
801
+ 'name': stack.name,
802
+ 'line': stack.line
803
+ }
804
+ for stack in stacks
805
+ ]
806
+
807
+ return params
808
+
809
+
810
+ def get_arg_info(func: Callable) -> list[
811
+ dict[
812
+ Literal['name', 'type', 'annotation', 'default'],
813
+ str | None
814
+ ]
815
+ ]:
816
+ """
817
+ Get function arguments information.
818
+
819
+ Parameters
820
+ ----------
821
+ func : Function.
822
+
823
+ Returns
824
+ -------
825
+ Arguments information.
826
+ - `Value of key 'name'`: Argument name.
827
+ - `Value of key 'type'`: Argument bind type.
828
+ `Literal['position_or_keyword']`: Is positional argument or keyword argument.
829
+ `Literal['var_position']`: Is variable length positional argument.
830
+ `Literal['var_keyword']`: Is variable length keyword argument.
831
+ `Literal['only_position']`: Is positional only argument.
832
+ `Literal['only_keyword']`: Is keyword only argument.
833
+ - `Value of key 'annotation'`: Argument annotation.
834
+ - `Value of key 'default'`: Argument default value.
835
+ """
836
+
837
+ # Get signature.
838
+ signature = inspect_signature(func)
839
+
840
+ # Get information.
841
+ info = [
842
+ {
843
+ 'name': name,
844
+ 'type': (
845
+ 'position_or_keyword'
846
+ if parameter.kind == _ParameterKind.POSITIONAL_OR_KEYWORD
847
+ else 'var_position'
848
+ if parameter.kind == _ParameterKind.VAR_POSITIONAL
849
+ else 'var_keyword'
850
+ if parameter.kind == _ParameterKind.VAR_KEYWORD
851
+ else 'only_position'
852
+ if parameter.kind == _ParameterKind.POSITIONAL_ONLY
853
+ else 'only_keyword'
854
+ if parameter.kind == _ParameterKind.KEYWORD_ONLY
855
+ else None
856
+ ),
857
+ 'annotation': parameter.annotation,
858
+ 'default': parameter.default
859
+ }
860
+ for name, parameter in signature.parameters.items()
861
+ ]
862
+
863
+ # Replace empty.
864
+ for row in info:
865
+ for key, value in row.items():
866
+ if value == _empty:
867
+ row[key] = None
868
+
869
+ return info
870
+
871
+
872
+ @overload
873
+ def get_name(obj: tuple, frame: int = 2) -> tuple[str, ...] | None: ...
874
+
875
+ @overload
876
+ def get_name(obj: Any, frame: int = 2) -> str | None: ...
877
+
878
+ def get_name(obj: Any, frame: int = 2) -> str | tuple[str, ...] | None:
879
+ """
880
+ Get name of object or variable.
881
+
882
+ Parameters
883
+ ----------
884
+ obj : Object.
885
+ - `tuple`: Variable length position parameter of previous layer.
886
+ - `Any`: Parameter of any layer.
887
+ frame : Number of code to upper level.
888
+
889
+ Returns
890
+ -------
891
+ Name or None.
892
+ """
893
+
894
+ # Get name using built in method.
895
+ if hasattr(obj, '__name__'):
896
+ name = obj.__name__
897
+ return name
898
+
899
+ # Get name using module method.
900
+ name = 'obj'
901
+ for frame_ in range(1, frame + 1):
902
+ if type(name) != str:
903
+ return
904
+ try:
905
+ name = argname(name, frame=frame_)
906
+ except VarnameRetrievingError:
907
+ return
908
+ if type(name) == tuple:
909
+ for element in name:
910
+ if type(element) != str:
911
+ return
912
+
913
+ return name
914
+
915
+
916
+ def block() -> None:
917
+ """
918
+ Blocking program, can be double press interrupt to end blocking.
919
+ """
920
+
921
+ # Start.
922
+ print('Start blocking.')
923
+ while True:
924
+ try:
925
+ time_sleep(1)
926
+ except KeyboardInterrupt:
927
+
928
+ # Confirm.
929
+ try:
930
+ print('Double press interrupt to end blocking.')
931
+ time_sleep(1)
932
+
933
+ # End.
934
+ except KeyboardInterrupt:
935
+ print('End blocking.')
936
+ break
937
+
938
+ except:
939
+ continue
940
+
941
+
942
+ def at_exit(*contents: str | Callable | tuple[Callable, Iterable, Mapping]) -> list[Callable]:
943
+ """
944
+ At exiting print text or execute function.
945
+
946
+ Parameters
947
+ ----------
948
+ contents : execute contents.
949
+ - `str`: Define the print text function and execute it.
950
+ - `Callable`: Execute function.
951
+ - `tuple[Callable, Iterable, Mapping]`: Execute function and position arguments and keyword arguments.
952
+
953
+ Returns
954
+ -------
955
+ Execute functions.
956
+ """
957
+
958
+ # Register.
959
+ funcs = []
960
+ for content in reversed(contents):
961
+ args = ()
962
+ kwargs = {}
963
+ if type(content) == str:
964
+ func = lambda : print(content)
965
+ elif callable(content):
966
+ func = content
967
+ elif type(content) == tuple:
968
+ func, args, kwargs = content
969
+ funcs.append(func)
970
+ atexit_register(func, *args, **kwargs)
971
+ funcs = list(reversed(funcs))
972
+
973
+ return funcs