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/rsystem.py ADDED
@@ -0,0 +1,1180 @@
1
+ # !/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+
4
+ """
5
+ @Time : 2022-12-05 14:09:42
6
+ @Author : Rey
7
+ @Contact : reyxbo@163.com
8
+ @Explain : Interpreter system methods.
9
+ """
10
+
11
+
12
+ from typing import Any, TypedDict, Literal, Optional, Union, overload
13
+ from collections.abc import Callable, Iterable, Sequence
14
+ from inspect import signature as inspect_signature, _ParameterKind, _empty
15
+ from sys import path as sys_path, modules as sys_modules
16
+ from os import getpid as os_getpid
17
+ from os.path import abspath as os_abspath
18
+ from psutil import (
19
+ boot_time as psutil_boot_time,
20
+ cpu_count as psutil_cpu_count,
21
+ cpu_freq as psutil_cpu_freq,
22
+ cpu_percent as psutil_cpu_percent,
23
+ virtual_memory as psutil_virtual_memory,
24
+ disk_partitions as psutil_disk_partitions,
25
+ disk_usage as psutil_disk_usage,
26
+ pids as psutil_pids,
27
+ net_connections as psutil_net_connections,
28
+ users as psutil_users,
29
+ net_connections as psutil_net_connections,
30
+ process_iter as psutil_process_iter,
31
+ pid_exists as psutil_pid_exists,
32
+ Process
33
+ )
34
+ from traceback import format_stack, extract_stack
35
+ from subprocess import Popen, PIPE
36
+ from pymem import Pymem
37
+ from argparse import ArgumentParser
38
+ from time import sleep as time_sleep
39
+ from datetime import datetime
40
+ from varname import VarnameRetrievingError, argname
41
+ from webbrowser import open as webbrowser_open
42
+
43
+ from .rexception import throw
44
+ from .rtype import RConfigMeta
45
+
46
+
47
+ __all__ = (
48
+ 'RConfigSystem',
49
+ 'add_env_path',
50
+ 'reset_env_path',
51
+ 'del_modules',
52
+ 'dos_command',
53
+ 'dos_command_var',
54
+ 'block',
55
+ 'is_iterable',
56
+ 'is_table',
57
+ 'is_number_str',
58
+ 'get_first_notnull',
59
+ 'get_name',
60
+ 'get_stack_text',
61
+ 'get_stack_param',
62
+ 'get_arg_info',
63
+ 'get_computer_info',
64
+ 'get_network_table',
65
+ 'get_process_table',
66
+ 'search_process',
67
+ 'kill_process',
68
+ 'stop_process',
69
+ 'start_process',
70
+ 'get_idle_port',
71
+ 'open_browser'
72
+ )
73
+
74
+
75
+ LoginUsers = TypedDict('LoginUsers', {'time': datetime, 'name': str, 'host': str})
76
+ ComputerInfo = TypedDict(
77
+ 'ComputerInfo',
78
+ {
79
+ 'boot_time': float,
80
+ 'cpu_count': int,
81
+ 'cpu_frequency': int,
82
+ 'cpu_percent': float,
83
+ 'memory_total': float,
84
+ 'memory_percent': float,
85
+ 'disk_total': float,
86
+ 'disk_percent': float,
87
+ 'process_count': int,
88
+ 'network_count': int,
89
+ 'login_users':LoginUsers
90
+ }
91
+ )
92
+ NetWorkInfo = TypedDict(
93
+ 'NetWorkTable',
94
+ {
95
+ 'family': Optional[str],
96
+ 'socket': Optional[str],
97
+ 'local_ip': str,
98
+ 'local_port': int,
99
+ 'remote_ip': Optional[str],
100
+ 'remote_port': Optional[int],
101
+ 'status': Optional[str],
102
+ 'pid': Optional[int]
103
+ }
104
+ )
105
+ ProcessInfo = TypedDict('ProcessInfo', {'create_time': datetime, 'id': int, 'name': str, 'ports': Optional[list[int]]})
106
+
107
+
108
+ class RConfigSystem(object, metaclass=RConfigMeta):
109
+ """
110
+ Rey's `config system` type.
111
+ """
112
+
113
+ # Added environment path.
114
+ _add_env_paths: list[str] = []
115
+
116
+
117
+ def add_env_path(path: str) -> list[str]:
118
+ """
119
+ Add environment variable path.
120
+
121
+ Parameters
122
+ ----------
123
+ path : Path, can be a relative path.
124
+
125
+ Returns
126
+ -------
127
+ Added environment variables list.
128
+ """
129
+
130
+ # Absolute path.
131
+ abs_path = os_abspath(path)
132
+
133
+ # Add.
134
+ RConfigSystem._add_env_paths.append(abs_path)
135
+ sys_path.append(abs_path)
136
+
137
+ return sys_path
138
+
139
+
140
+ def reset_env_path() -> None:
141
+ """
142
+ Reset environment variable path.
143
+ """
144
+
145
+ # Delete.
146
+ for path in RConfigSystem._add_env_paths:
147
+ sys_path.remove(path)
148
+ RConfigSystem._add_env_paths = []
149
+
150
+
151
+ def del_modules(path: str) -> list[str]:
152
+ """
153
+ Delete record of modules import dictionary.
154
+
155
+ Parameters
156
+ ----------
157
+ path : Module path, use regular match.
158
+
159
+ Returns
160
+ -------
161
+ Deleted modules dictionary.
162
+ """
163
+
164
+ # Import.
165
+ from .rregex import search
166
+
167
+ # Set parameter.
168
+ deleted_dict = {}
169
+ module_keys = tuple(sys_modules.keys())
170
+
171
+ # Delete.
172
+ for key in module_keys:
173
+ module = sys_modules.get(key)
174
+
175
+ ## Filter non file module.
176
+ if (
177
+ not hasattr(module, '__file__')
178
+ or module.__file__ is None
179
+ ):
180
+ continue
181
+
182
+ ## Match.
183
+ result = search(path, module.__file__)
184
+ if result is None:
185
+ continue
186
+
187
+ ## Take out.
188
+ deleted_dict[key] = sys_modules.pop(key)
189
+
190
+ return deleted_dict
191
+
192
+
193
+ def dos_command(command: Union[str, Iterable[str]]) -> str:
194
+ """
195
+ Execute DOS command.
196
+
197
+ Parameters
198
+ ----------
199
+ command : DOS command.
200
+ - `str`: Use this command.
201
+ - `Iterable[str]`: Join strings with space as command.
202
+ When space in the string, automatic add quotation mark (e.g., ['echo', 'a b'] -> 'echo 'a b'').
203
+
204
+ Returns
205
+ -------
206
+ Command standard output.
207
+ """
208
+
209
+ # Execute.
210
+ popen = Popen(command, shell=True, stdout=PIPE, stderr=PIPE)
211
+
212
+ # Check.
213
+ error_bytes: bytes = popen.stderr.read()
214
+ if error_bytes != b'':
215
+ error = error_bytes.decode('GBK')
216
+ throw(value=error)
217
+
218
+ # Standard output.
219
+ output_bytes: bytes = popen.stdout.read()
220
+ output = output_bytes.decode('GBK')
221
+
222
+ return output
223
+
224
+
225
+ def dos_command_var(*vars: Any) -> list[Any]:
226
+ """
227
+ Use DOS command to input arguments to variables.
228
+ Use DOS command `python file --help` to view help information.
229
+
230
+ Parameters
231
+ ----------
232
+ vars : Variables.
233
+
234
+ Returns
235
+ -------
236
+ Value of variables.
237
+
238
+ Examples
239
+ --------
240
+ >>> var1 = 1
241
+ >>> var2 = 2
242
+ >>> var3 = 3
243
+ >>> var1, var2, var3 = dos_command(var1, var2, var3)
244
+ >>> print(var1, var2, var3)
245
+ >>> # Use DOS command 'python file.py 10 --var2 20 21'
246
+ 10 [20, 21] 3
247
+ """
248
+
249
+ # Get parameter.
250
+ vars_name = get_name(vars)
251
+ vars_info = tuple(zip(vars_name, vars))
252
+
253
+ # Set DOS command.
254
+ usage = 'input arguments to variables'
255
+ parser = ArgumentParser(usage=usage)
256
+ for name, value in vars_info:
257
+ if value is None:
258
+ var_type = str
259
+ var_help = None
260
+ else:
261
+ var_type = value.__class__
262
+ var_help = str(value.__class__)
263
+
264
+ ## Position argument.
265
+ parser.add_argument(
266
+ name,
267
+ nargs='?',
268
+ type=var_type,
269
+ help=var_help
270
+ )
271
+
272
+ ## Keyword argument.
273
+ kw_name = '--' + name
274
+ parser.add_argument(
275
+ kw_name,
276
+ nargs='*',
277
+ type=var_type,
278
+ help=var_help,
279
+ metavar='value',
280
+ dest=kw_name
281
+ )
282
+
283
+ # Get argument.
284
+ namespace = parser.parse_args()
285
+ values = []
286
+ for name, value in vars_info:
287
+ kw_name = '--' + name
288
+
289
+ ## Position argument.
290
+ dos_value = getattr(namespace, name)
291
+ if dos_value is not None:
292
+ values.append(dos_value)
293
+ continue
294
+
295
+ ## Keyword argument.
296
+ dos_value = getattr(namespace, kw_name)
297
+ if dos_value.__class__ == list:
298
+ value_len = len(dos_value)
299
+ match value_len:
300
+ case 0:
301
+ dos_value = None
302
+ case 1:
303
+ dos_value = dos_value[0]
304
+ values.append(dos_value)
305
+ continue
306
+
307
+ values.append(value)
308
+
309
+ return values
310
+
311
+
312
+ def block() -> None:
313
+ """
314
+ Blocking program, can be double press interrupt to end blocking.
315
+ """
316
+
317
+ # Start.
318
+ print('Start blocking.')
319
+ while True:
320
+ try:
321
+ time_sleep(1)
322
+ except KeyboardInterrupt:
323
+
324
+ # Confirm.
325
+ try:
326
+ print('Double press interrupt to end blocking.')
327
+ time_sleep(1)
328
+
329
+ # End.
330
+ except KeyboardInterrupt:
331
+ print('End blocking.')
332
+ break
333
+
334
+ except:
335
+ continue
336
+
337
+
338
+ def is_iterable(
339
+ obj: Any,
340
+ exclude_types: Iterable[type] = [str, bytes]
341
+ ) -> bool:
342
+ """
343
+ Judge whether it is iterable.
344
+
345
+ Parameters
346
+ ----------
347
+ obj : Judge object.
348
+ exclude_types : Non iterative types.
349
+
350
+ Returns
351
+ -------
352
+ Judgment result.
353
+ """
354
+
355
+ # Exclude types.
356
+ if obj.__class__ in exclude_types:
357
+ return False
358
+
359
+ # Judge.
360
+ if hasattr(obj, '__iter__'):
361
+ return True
362
+ else:
363
+ return False
364
+
365
+
366
+ def is_table(
367
+ obj: Any,
368
+ check_fields: bool = True
369
+ ) -> bool:
370
+ """
371
+ Judge whether it is `list[dict]` table format and keys and keys sort of the dict are the same.
372
+
373
+ Parameters
374
+ ----------
375
+ obj : Judge object.
376
+ check_fields : Do you want to check the keys and keys sort of the dict are the same.
377
+
378
+ Returns
379
+ -------
380
+ Judgment result.
381
+ """
382
+
383
+ # Judge.
384
+ if obj.__class__ != list:
385
+ return False
386
+ for element in obj:
387
+ if element.__class__ != dict:
388
+ return False
389
+
390
+ ## Check fields of table.
391
+ if check_fields:
392
+ keys_strs = [
393
+ ':'.join([str(key) for key in element.keys()])
394
+ for element in obj
395
+ ]
396
+ keys_strs_only = set(keys_strs)
397
+ if len(keys_strs_only) != 1:
398
+ return False
399
+
400
+ return True
401
+
402
+
403
+ def is_number_str(
404
+ string: str
405
+ ) -> bool:
406
+ """
407
+ Judge whether it is number string.
408
+
409
+ Parameters
410
+ ----------
411
+ string : String.
412
+
413
+ Returns
414
+ -------
415
+ Judgment result.
416
+ """
417
+
418
+ # Judge.
419
+ try:
420
+ float(string)
421
+ except (ValueError, TypeError):
422
+ return False
423
+
424
+ return True
425
+
426
+
427
+ def get_first_notnull(
428
+ *values: Any,
429
+ default: Union[None, Any, Literal['exception']] = None,
430
+ nulls: tuple = (None,)) -> Any:
431
+ """
432
+ Get the first value that is not null.
433
+
434
+ Parameters
435
+ ----------
436
+ values : Check values.
437
+ default : When all are null, then return this is value, or throw exception.
438
+ - `Any`: Return this is value.
439
+ - `Literal['exception']`: Throw `exception`.
440
+ nulls : Range of null values.
441
+
442
+ Returns
443
+ -------
444
+ Return first not null value, when all are `None`, then return default value.
445
+ """
446
+
447
+ # Get value.
448
+ for value in values:
449
+ if value not in nulls:
450
+ return value
451
+
452
+ # Throw exception.
453
+ if default == 'exception':
454
+ vars_name = get_name(values)
455
+ if vars_name is not None:
456
+ vars_name_de_dup = list(set(vars_name))
457
+ vars_name_de_dup.sort(key=vars_name.index)
458
+ vars_name_str = ' ' + ' and '.join([f'"{var_name}"' for var_name in vars_name_de_dup])
459
+ else:
460
+ vars_name_str = ''
461
+ raise ValueError(f'at least one of parameters{vars_name_str} is not None')
462
+
463
+ return default
464
+
465
+
466
+ @overload
467
+ def get_name(obj: tuple, frame: int = 2) -> Optional[tuple[str, ...]]: ...
468
+
469
+ @overload
470
+ def get_name(obj: Any, frame: int = 2) -> Optional[str]: ...
471
+
472
+ def get_name(obj: Any, frame: int = 2) -> Optional[Union[str, tuple[str, ...]]]:
473
+ """
474
+ Get name of object or variable.
475
+
476
+ Parameters
477
+ ----------
478
+ obj : Object.
479
+ - `tuple`: Variable length position parameter of previous layer.
480
+ - `Any`: Parameter of any layer.
481
+ frame : Number of code to upper level.
482
+
483
+ Returns
484
+ -------
485
+ Name or None.
486
+ """
487
+
488
+ # Get name using built in method.
489
+ if hasattr(obj, '__name__'):
490
+ name = obj.__name__
491
+ return name
492
+
493
+ # Get name using module method.
494
+ name = 'obj'
495
+ for frame_ in range(1, frame + 1):
496
+ if name.__class__ != str:
497
+ return
498
+ try:
499
+ name = argname(name, frame=frame_)
500
+ except VarnameRetrievingError:
501
+ return
502
+ if name.__class__ == tuple:
503
+ for element in name:
504
+ if element.__class__ != str:
505
+ return
506
+
507
+ return name
508
+
509
+
510
+ def get_stack_text(format_: Literal['plain', 'full'] = 'plain', limit: int = 2) -> str:
511
+ """
512
+ Get code stack text.
513
+
514
+ Parameters
515
+ ----------
516
+ format_ : Stack text format.
517
+ - `Literal['plain']`: Floor stack position.
518
+ - `Literal['full']`: Full stack information.
519
+ limit : Stack limit level.
520
+
521
+ Returns
522
+ -------
523
+ Code stack text.
524
+ """
525
+
526
+ match format_:
527
+
528
+ # Plain.
529
+ case 'plain':
530
+ limit += 1
531
+ stacks = format_stack(limit=limit)
532
+
533
+ ## Check.
534
+ if len(stacks) != limit:
535
+ throw(value=limit)
536
+
537
+ ## Convert.
538
+ text = stacks[0]
539
+ index_end = text.find(', in ')
540
+ text = text[2:index_end]
541
+
542
+ # Full.
543
+ case 'full':
544
+ stacks = format_stack()
545
+ index_limit = len(stacks) - limit
546
+ stacks = stacks[:index_limit]
547
+
548
+ ## Check.
549
+ if len(stacks) == 0:
550
+ throw(value=limit)
551
+
552
+ ## Convert.
553
+ stacks = [
554
+ stack[2:].replace('\n ', '\n', 1)
555
+ for stack in stacks
556
+ ]
557
+ text = ''.join(stacks)
558
+ text = text[:-1]
559
+
560
+ # Throw exception.
561
+ case _:
562
+ throw(ValueError, format_)
563
+
564
+ return text
565
+
566
+
567
+ @overload
568
+ def get_stack_param(format_: Literal['floor'] = 'floor', limit: int = 2) -> dict: ...
569
+
570
+ @overload
571
+ def get_stack_param(format_: Literal['full'] = 'floor', limit: int = 2) -> list[dict]: ...
572
+
573
+ def get_stack_param(format_: Literal['floor', 'full'] = 'floor', limit: int = 2) -> Union[dict, list[dict]]:
574
+ """
575
+ Get code stack parameters.
576
+
577
+ Parameters
578
+ ----------
579
+ format_ : Stack parameters format.
580
+ - `Literal['floor']`: Floor stack parameters.
581
+ - `Literal['full']`: Full stack parameters.
582
+ limit : Stack limit level.
583
+
584
+ Returns
585
+ -------
586
+ Code stack parameters.
587
+ """
588
+
589
+ # Get.
590
+ stacks = extract_stack()
591
+ index_limit = len(stacks) - limit
592
+ stacks = stacks[:index_limit]
593
+
594
+ # Check.
595
+ if len(stacks) == 0:
596
+ throw(value=limit)
597
+
598
+ # Convert.
599
+ match format_:
600
+
601
+ ## Floor.
602
+ case 'floor':
603
+ stack = stacks[-1]
604
+ params = {
605
+ 'filename': stack.filename,
606
+ 'lineno': stack.lineno,
607
+ 'name': stack.name,
608
+ 'line': stack.line
609
+ }
610
+
611
+ ## Full.
612
+ case 'full':
613
+ params = [
614
+ {
615
+ 'filename': stack.filename,
616
+ 'lineno': stack.lineno,
617
+ 'name': stack.name,
618
+ 'line': stack.line
619
+ }
620
+ for stack in stacks
621
+ ]
622
+
623
+ return params
624
+
625
+
626
+ def get_arg_info(func: Callable) -> list[
627
+ dict[
628
+ Literal['name', 'type', 'annotation', 'default'],
629
+ Optional[str]
630
+ ]
631
+ ]:
632
+ """
633
+ Get function arguments information.
634
+
635
+ Parameters
636
+ ----------
637
+ func : Function.
638
+
639
+ Returns
640
+ -------
641
+ Arguments information.
642
+ - `Value of key 'name'`: Argument name.
643
+ - `Value of key 'type'`: Argument bind type.
644
+ `Literal['position_or_keyword']`: Is positional argument or keyword argument.
645
+ `Literal['var_position']`: Is variable length positional argument.
646
+ `Literal['var_keyword']`: Is variable length keyword argument.
647
+ `Literal['only_position']`: Is positional only argument.
648
+ `Literal['only_keyword']`: Is keyword only argument.
649
+ - `Value of key 'annotation'`: Argument annotation.
650
+ - `Value of key 'default'`: Argument default value.
651
+ """
652
+
653
+ # Get signature.
654
+ signature = inspect_signature(func)
655
+
656
+ # Get information.
657
+ info = [
658
+ {
659
+ 'name': name,
660
+ 'type': (
661
+ 'position_or_keyword'
662
+ if parameter.kind == _ParameterKind.POSITIONAL_OR_KEYWORD
663
+ else 'var_position'
664
+ if parameter.kind == _ParameterKind.VAR_POSITIONAL
665
+ else 'var_keyword'
666
+ if parameter.kind == _ParameterKind.VAR_KEYWORD
667
+ else 'only_position'
668
+ if parameter.kind == _ParameterKind.POSITIONAL_ONLY
669
+ else 'only_keyword'
670
+ if parameter.kind == _ParameterKind.KEYWORD_ONLY
671
+ else None
672
+ ),
673
+ 'annotation': parameter.annotation,
674
+ 'default': parameter.default
675
+ }
676
+ for name, parameter in signature.parameters.items()
677
+ ]
678
+
679
+ # Replace empty.
680
+ for row in info:
681
+ for key, value in row.items():
682
+ if value == _empty:
683
+ row[key] = None
684
+
685
+ return info
686
+
687
+
688
+ def get_computer_info() -> ComputerInfo:
689
+ """
690
+ Get computer information.
691
+
692
+ Returns
693
+ -------
694
+ Computer information dictionary.
695
+ - `Key 'boot_time'`: Computer boot time.
696
+ - `Key 'cpu_count'`: Computer logical CPU count.
697
+ - `Key 'cpu_frequency'`: Computer current CPU frequency.
698
+ - `Key 'cpu_percent'`: Computer CPU usage percent.
699
+ - `Key 'memory_total'`: Computer memory total gigabyte.
700
+ - `Key 'memory_percent'`: Computer memory usage percent.
701
+ - `Key 'disk_total'`: Computer disk total gigabyte.
702
+ - `Key 'disk_percent'`: Computer disk usage percent.
703
+ - `Key 'process_count'`: Computer process count.
704
+ - `Key 'network_count'`: Computer network count.
705
+ - `Key 'login_users'`: Computer login users information.
706
+ """
707
+
708
+ # Set parameter.
709
+ info = {}
710
+
711
+ # Get.
712
+
713
+ ## Boot time.
714
+ boot_time = psutil_boot_time()
715
+ info['boot_time'] = datetime.fromtimestamp(
716
+ boot_time
717
+ ).strftime(
718
+ '%Y-%m-%d %H:%M:%S'
719
+ )
720
+
721
+ ## CPU.
722
+ info['cpu_count'] = psutil_cpu_count()
723
+ info['cpu_frequency'] = int(psutil_cpu_freq().current)
724
+ info['cpu_percent'] = round(psutil_cpu_percent(), 1)
725
+
726
+ ## Memory.
727
+ memory_info = psutil_virtual_memory()
728
+ info['memory_total'] = round(memory_info.total / 1024 / 1024 / 1024, 1)
729
+ info['memory_percent'] = round(memory_info.percent, 1)
730
+
731
+ ## Disk.
732
+ disk_total = []
733
+ disk_used = []
734
+ partitions_info = psutil_disk_partitions()
735
+ for partition_info in partitions_info:
736
+ try:
737
+ partition_usage_info = psutil_disk_usage(partition_info.device)
738
+ except PermissionError:
739
+ continue
740
+ disk_total.append(partition_usage_info.total)
741
+ disk_used.append(partition_usage_info.used)
742
+ disk_total = sum(disk_total)
743
+ disk_used = sum(disk_used)
744
+ info['disk_total'] = round(disk_total / 1024 / 1024 / 1024, 1)
745
+ info['disk_percent'] = round(disk_used / disk_total * 100, 1)
746
+
747
+ ## Process.
748
+ pids = psutil_pids()
749
+ info['process_count'] = len(pids)
750
+
751
+ ## Network.
752
+ net_info = psutil_net_connections()
753
+ info['network_count'] = len(net_info)
754
+
755
+ ## User.
756
+ users_info = psutil_users()
757
+ info['login_users'] = [
758
+ {
759
+ 'time': datetime.fromtimestamp(
760
+ user_info.started
761
+ ).strftime(
762
+ '%Y-%m-%d %H:%M:%S'
763
+ ),
764
+ 'name': user_info.name,
765
+ 'host': user_info.host
766
+ }
767
+ for user_info in users_info
768
+ ]
769
+ sort_func = lambda row: row['time']
770
+ info['login_users'].sort(key=sort_func, reverse=True)
771
+
772
+ return info
773
+
774
+
775
+ def get_network_table() -> list[NetWorkInfo]:
776
+ """
777
+ Get network information table.
778
+
779
+ Returns
780
+ -------
781
+ Network information table.
782
+ """
783
+
784
+ # Get.
785
+ connections = psutil_net_connections('all')
786
+ table = [
787
+ {
788
+ 'family': (
789
+ 'IPv4'
790
+ if connection.family.name == 'AF_INET'
791
+ else 'IPv6'
792
+ if connection.family.name == 'AF_INET6'
793
+ else None
794
+ ),
795
+ 'socket': (
796
+ 'TCP'
797
+ if connection.type.name == 'SOCK_STREAM'
798
+ else 'UDP'
799
+ if connection.type.name == 'SOCK_DGRAM'
800
+ else None
801
+ ),
802
+ 'local_ip': connection.laddr.ip,
803
+ 'local_port': connection.laddr.port,
804
+ 'remote_ip': (
805
+ None
806
+ if connection.raddr == ()
807
+ else connection.raddr.ip
808
+ ),
809
+ 'remote_port': (
810
+ None
811
+ if connection.raddr == ()
812
+ else connection.raddr.port
813
+ ),
814
+ 'status': (
815
+ None
816
+ if connection.status == 'NONE'
817
+ else connection.status.lower()
818
+ ),
819
+ 'pid': connection.pid
820
+ }
821
+ for connection in connections
822
+ ]
823
+
824
+ # Sort.
825
+ sort_func = lambda row: row['local_port']
826
+ table.sort(key=sort_func)
827
+ sort_func = lambda row: row['local_ip']
828
+ table.sort(key=sort_func)
829
+
830
+ return table
831
+
832
+
833
+ def get_process_table() -> list[ProcessInfo]:
834
+ """
835
+ Get process information table.
836
+
837
+ Returns
838
+ -------
839
+ Process information table.
840
+ """
841
+
842
+ # Get.
843
+ process_iter = psutil_process_iter()
844
+ table = []
845
+ for process in process_iter:
846
+ info = {}
847
+ with process.oneshot():
848
+ info['create_time'] = datetime.fromtimestamp(
849
+ process.create_time()
850
+ ).strftime(
851
+ '%Y-%m-%d %H:%M:%S'
852
+ )
853
+ info['id'] = process.pid
854
+ info['name'] = process.name()
855
+ connections = process.connections()
856
+ if connections == []:
857
+ info['ports'] = None
858
+ else:
859
+ info['ports'] = [
860
+ connection.laddr.port
861
+ for connection in connections
862
+ ]
863
+ table.append(info)
864
+
865
+ # Sort.
866
+ sort_func = lambda row: row['id']
867
+ table.sort(key=sort_func)
868
+ sort_func = lambda row: row['create_time']
869
+ table.sort(key=sort_func)
870
+
871
+ return table
872
+
873
+
874
+ def search_process(
875
+ id_: Optional[Union[int, Sequence[int]]] = None,
876
+ name: Optional[Union[str, Sequence[str]]] = None,
877
+ port: Optional[Union[str, int, Sequence[Union[str, int]]]] = None,
878
+ ) -> list[Process]:
879
+ """
880
+ Search process by ID or name or port.
881
+
882
+ Parameters
883
+ ----------
884
+ id_ : Search condition, a value or sequence of process ID.
885
+ name : Search condition, a value or sequence of process name.
886
+ port : Search condition, a value or sequence of process port.
887
+
888
+ Returns
889
+ -------
890
+ List of process instances that match any condition.
891
+ """
892
+
893
+ # Handle parameter.
894
+ match id_:
895
+ case None:
896
+ ids = []
897
+ case int():
898
+ ids = [id_]
899
+ case _:
900
+ ids = id_
901
+ match name:
902
+ case None:
903
+ names = []
904
+ case str():
905
+ names = [name]
906
+ case _:
907
+ names = name
908
+ match port:
909
+ case None:
910
+ ports = []
911
+ case str() | int():
912
+ ports = [port]
913
+ case _:
914
+ ports = port
915
+ ports = [
916
+ int(port)
917
+ for port in ports
918
+ ]
919
+
920
+ # Search.
921
+ processes = []
922
+ if (
923
+ names != []
924
+ or ports != []
925
+ ):
926
+ table = get_process_table()
927
+ else:
928
+ table = []
929
+
930
+ ## ID.
931
+ for id__ in ids:
932
+ if psutil_pid_exists(id__):
933
+ process = Process(id__)
934
+ processes.append(process)
935
+
936
+ ## Name.
937
+ for info in table:
938
+ if (
939
+ info['name'] in names
940
+ and psutil_pid_exists(info['id'])
941
+ ):
942
+ process = Process(info['id'])
943
+ processes.append(process)
944
+
945
+ ## Port.
946
+ for info in table:
947
+ for port in ports:
948
+ if (
949
+ info['ports'] is not None
950
+ and port in info['ports']
951
+ and psutil_pid_exists(info['id'])
952
+ ):
953
+ process = Process(info['id'])
954
+ processes.append(process)
955
+ break
956
+
957
+ return processes
958
+
959
+
960
+ def kill_process(
961
+ id_: Optional[Union[int, Sequence[int]]] = None,
962
+ name: Optional[Union[str, Sequence[str]]] = None,
963
+ port: Optional[Union[str, int, Sequence[Union[str, int]]]] = None,
964
+ ) -> list[Process]:
965
+ """
966
+ Search and kill process by ID or name or port.
967
+
968
+ Parameters
969
+ ----------
970
+ id_ : Search condition, a value or sequence of process ID.
971
+ name : Search condition, a value or sequence of process name.
972
+ port : Search condition, a value or sequence of process port.
973
+
974
+ Returns
975
+ -------
976
+ List of process instances that match any condition.
977
+ """
978
+
979
+ # Get parameter.
980
+ self_pid = os_getpid()
981
+
982
+ # Search.
983
+ processes = search_process(id_, name, port)
984
+
985
+ # Start.
986
+ for process in processes:
987
+ with process.oneshot():
988
+
989
+ ## Filter self process.
990
+ if process.pid == self_pid:
991
+ continue
992
+
993
+ process.kill()
994
+
995
+ return processes
996
+
997
+
998
+ def stop_process(
999
+ id_: Optional[Union[int, Sequence[int]]] = None,
1000
+ name: Optional[Union[str, Sequence[str]]] = None,
1001
+ port: Optional[Union[str, int, Sequence[Union[str, int]]]] = None,
1002
+ ) -> list[Process]:
1003
+ """
1004
+ Search and stop process by ID or name or port.
1005
+
1006
+ Parameters
1007
+ ----------
1008
+ id_ : Search condition, a value or sequence of process ID.
1009
+ name : Search condition, a value or sequence of process name.
1010
+ port : Search condition, a value or sequence of process port.
1011
+
1012
+ Returns
1013
+ -------
1014
+ List of process instances that match any condition.
1015
+ """
1016
+
1017
+ # Get parameter.
1018
+ self_pid = os_getpid()
1019
+
1020
+ # Search.
1021
+ processes = search_process(id_, name, port)
1022
+
1023
+ # Start.
1024
+ for process in processes:
1025
+ with process.oneshot():
1026
+
1027
+ ## Filter self process.
1028
+ if process.pid == self_pid:
1029
+ continue
1030
+
1031
+ process.suspend()
1032
+
1033
+ return processes
1034
+
1035
+
1036
+ def start_process(
1037
+ id_: Optional[Union[int, Sequence[int]]] = None,
1038
+ name: Optional[Union[str, Sequence[str]]] = None,
1039
+ port: Optional[Union[str, int, Sequence[Union[str, int]]]] = None,
1040
+ ) -> list[Process]:
1041
+ """
1042
+ Search and start process by ID or name or port.
1043
+
1044
+ Parameters
1045
+ ----------
1046
+ id_ : Search condition, a value or sequence of process ID.
1047
+ name : Search condition, a value or sequence of process name.
1048
+ port : Search condition, a value or sequence of process port.
1049
+
1050
+ Returns
1051
+ -------
1052
+ List of process instances that match any condition.
1053
+ """
1054
+
1055
+ # Search.
1056
+ processes = search_process(id_, name, port)
1057
+
1058
+ # Start.
1059
+ for process in processes:
1060
+ with process.oneshot():
1061
+ process.resume()
1062
+
1063
+ return processes
1064
+
1065
+
1066
+ def get_idle_port(min: int = 49152) -> int:
1067
+ """
1068
+ Judge and get an idle port number.
1069
+
1070
+ Parameters
1071
+ ----------
1072
+ min : Minimum port number.
1073
+
1074
+ Returns
1075
+ -------
1076
+ Idle port number.
1077
+ """
1078
+
1079
+ # Get parameter.
1080
+ network_table = get_network_table()
1081
+ ports = [
1082
+ info['local_port']
1083
+ for info in network_table
1084
+ ]
1085
+
1086
+ # Judge.
1087
+ while True:
1088
+ if min in ports:
1089
+ min += 1
1090
+ else:
1091
+ return min
1092
+
1093
+
1094
+ def memory_read(
1095
+ process: Union[int, str],
1096
+ dll: str,
1097
+ offset: int
1098
+ ) -> int:
1099
+ """
1100
+ Read memory value.
1101
+
1102
+ Parameters
1103
+ ----------
1104
+ process : Process ID or name.
1105
+ dll : DLL file name.
1106
+ offset : Memory address offset.
1107
+
1108
+ Returns
1109
+ -------
1110
+ Memory value.
1111
+ """
1112
+
1113
+ # Get DLL address.
1114
+ pymem = Pymem(process)
1115
+ for module in pymem.list_modules():
1116
+ if module.name == dll:
1117
+ dll_address: int = module.lpBaseOfDll
1118
+ break
1119
+
1120
+ ## Throw exception.
1121
+ else:
1122
+ throw(value=dll_address)
1123
+
1124
+ # Get memory address.
1125
+ memory_address = dll_address + offset
1126
+
1127
+ # Read.
1128
+ value = pymem.read_int(memory_address)
1129
+
1130
+ return value
1131
+
1132
+
1133
+ def memory_write(
1134
+ process: Union[int, str],
1135
+ dll: str,
1136
+ offset: int,
1137
+ value: int
1138
+ ) -> None:
1139
+ """
1140
+ Write memory value.
1141
+
1142
+ Parameters
1143
+ ----------
1144
+ process : Process ID or name.
1145
+ dll : DLL file name.
1146
+ offset : Memory address offset.
1147
+ value : Memory value.
1148
+ """
1149
+
1150
+ # Get DLL address.
1151
+ pymem = Pymem(process)
1152
+ for module in pymem.list_modules():
1153
+ if module.name == dll:
1154
+ dll_address: int = module.lpBaseOfDll
1155
+ break
1156
+
1157
+ # Get memory address.
1158
+ memory_address = dll_address + offset
1159
+
1160
+ # Read.
1161
+ pymem.write_int(memory_address, value)
1162
+
1163
+
1164
+ def open_browser(url: str) -> bool:
1165
+ """
1166
+ Open browser and URL.
1167
+
1168
+ Parameters
1169
+ ----------
1170
+ url : URL.
1171
+
1172
+ Returns
1173
+ -------
1174
+ Is it successful.
1175
+ """
1176
+
1177
+ # Open.
1178
+ succeeded = webbrowser_open(url)
1179
+
1180
+ return succeeded