python-misc-utils 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.
Files changed (117) hide show
  1. py_misc_utils/__init__.py +0 -0
  2. py_misc_utils/abs_timeout.py +12 -0
  3. py_misc_utils/alog.py +311 -0
  4. py_misc_utils/app_main.py +179 -0
  5. py_misc_utils/archive_streamer.py +112 -0
  6. py_misc_utils/assert_checks.py +118 -0
  7. py_misc_utils/ast_utils.py +121 -0
  8. py_misc_utils/async_manager.py +189 -0
  9. py_misc_utils/break_control.py +63 -0
  10. py_misc_utils/buffered_iterator.py +35 -0
  11. py_misc_utils/cached_file.py +507 -0
  12. py_misc_utils/call_limiter.py +26 -0
  13. py_misc_utils/call_result_selector.py +13 -0
  14. py_misc_utils/cleanups.py +85 -0
  15. py_misc_utils/cmd.py +97 -0
  16. py_misc_utils/compression.py +116 -0
  17. py_misc_utils/cond_waiter.py +13 -0
  18. py_misc_utils/context_base.py +18 -0
  19. py_misc_utils/context_managers.py +67 -0
  20. py_misc_utils/core_utils.py +577 -0
  21. py_misc_utils/daemon_process.py +252 -0
  22. py_misc_utils/data_cache.py +46 -0
  23. py_misc_utils/date_utils.py +90 -0
  24. py_misc_utils/debug.py +24 -0
  25. py_misc_utils/dyn_modules.py +50 -0
  26. py_misc_utils/dynamod.py +103 -0
  27. py_misc_utils/env_config.py +35 -0
  28. py_misc_utils/executor.py +239 -0
  29. py_misc_utils/file_overwrite.py +29 -0
  30. py_misc_utils/fin_wrap.py +77 -0
  31. py_misc_utils/fp_utils.py +47 -0
  32. py_misc_utils/fs/__init__.py +0 -0
  33. py_misc_utils/fs/file_fs.py +127 -0
  34. py_misc_utils/fs/ftp_fs.py +242 -0
  35. py_misc_utils/fs/gcs_fs.py +196 -0
  36. py_misc_utils/fs/http_fs.py +241 -0
  37. py_misc_utils/fs/s3_fs.py +417 -0
  38. py_misc_utils/fs_base.py +133 -0
  39. py_misc_utils/fs_utils.py +207 -0
  40. py_misc_utils/gcs_fs.py +169 -0
  41. py_misc_utils/gen_indices.py +54 -0
  42. py_misc_utils/gfs.py +371 -0
  43. py_misc_utils/git_repo.py +77 -0
  44. py_misc_utils/global_namespace.py +110 -0
  45. py_misc_utils/http_async_fetcher.py +139 -0
  46. py_misc_utils/http_server.py +196 -0
  47. py_misc_utils/http_utils.py +143 -0
  48. py_misc_utils/img_utils.py +20 -0
  49. py_misc_utils/infix_op.py +20 -0
  50. py_misc_utils/inspect_utils.py +205 -0
  51. py_misc_utils/iostream.py +21 -0
  52. py_misc_utils/iter_file.py +117 -0
  53. py_misc_utils/key_wrap.py +46 -0
  54. py_misc_utils/lazy_import.py +25 -0
  55. py_misc_utils/lockfile.py +164 -0
  56. py_misc_utils/mem_size.py +64 -0
  57. py_misc_utils/mirror_from.py +72 -0
  58. py_misc_utils/mmap.py +16 -0
  59. py_misc_utils/module_utils.py +196 -0
  60. py_misc_utils/moving_average.py +19 -0
  61. py_misc_utils/msgpack_streamer.py +26 -0
  62. py_misc_utils/multi_wait.py +24 -0
  63. py_misc_utils/multiprocessing.py +102 -0
  64. py_misc_utils/named_array.py +224 -0
  65. py_misc_utils/no_break.py +46 -0
  66. py_misc_utils/no_except.py +32 -0
  67. py_misc_utils/np_ml_framework.py +184 -0
  68. py_misc_utils/np_utils.py +346 -0
  69. py_misc_utils/ntuple_utils.py +38 -0
  70. py_misc_utils/num_utils.py +54 -0
  71. py_misc_utils/obj.py +73 -0
  72. py_misc_utils/object_cache.py +100 -0
  73. py_misc_utils/object_tracker.py +88 -0
  74. py_misc_utils/ordered_set.py +71 -0
  75. py_misc_utils/osfd.py +27 -0
  76. py_misc_utils/packet.py +22 -0
  77. py_misc_utils/parquet_streamer.py +69 -0
  78. py_misc_utils/pd_utils.py +254 -0
  79. py_misc_utils/periodic_task.py +61 -0
  80. py_misc_utils/pickle_wrap.py +121 -0
  81. py_misc_utils/pipeline.py +98 -0
  82. py_misc_utils/remap_pickle.py +50 -0
  83. py_misc_utils/resource_manager.py +155 -0
  84. py_misc_utils/rnd_utils.py +56 -0
  85. py_misc_utils/run_once.py +19 -0
  86. py_misc_utils/scheduler.py +135 -0
  87. py_misc_utils/select_params.py +300 -0
  88. py_misc_utils/signal.py +141 -0
  89. py_misc_utils/skl_utils.py +270 -0
  90. py_misc_utils/split.py +147 -0
  91. py_misc_utils/state.py +53 -0
  92. py_misc_utils/std_module.py +56 -0
  93. py_misc_utils/stream_dataframe.py +176 -0
  94. py_misc_utils/streamed_file.py +144 -0
  95. py_misc_utils/tempdir.py +79 -0
  96. py_misc_utils/template_replace.py +51 -0
  97. py_misc_utils/tensor_stream.py +269 -0
  98. py_misc_utils/thread_context.py +33 -0
  99. py_misc_utils/throttle.py +30 -0
  100. py_misc_utils/time_trigger.py +18 -0
  101. py_misc_utils/timegen.py +11 -0
  102. py_misc_utils/traceback.py +49 -0
  103. py_misc_utils/tracking_executor.py +91 -0
  104. py_misc_utils/transform_array.py +42 -0
  105. py_misc_utils/uncompress.py +35 -0
  106. py_misc_utils/url_fetcher.py +157 -0
  107. py_misc_utils/utils.py +538 -0
  108. py_misc_utils/varint.py +50 -0
  109. py_misc_utils/virt_array.py +52 -0
  110. py_misc_utils/weak_call.py +33 -0
  111. py_misc_utils/work_results.py +100 -0
  112. py_misc_utils/writeback_file.py +43 -0
  113. python_misc_utils-0.2.dist-info/METADATA +36 -0
  114. python_misc_utils-0.2.dist-info/RECORD +117 -0
  115. python_misc_utils-0.2.dist-info/WHEEL +5 -0
  116. python_misc_utils-0.2.dist-info/licenses/LICENSE +13 -0
  117. python_misc_utils-0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,196 @@
1
+ import argparse
2
+ import base64
3
+ import copy
4
+ import http.server
5
+ import os
6
+
7
+
8
+ def _sanitize_path(path):
9
+ if '..' not in path and not path.endswith('/'):
10
+ return path
11
+
12
+
13
+ def _read_stream(headers, stream, chunked_headers, chunk_size=None):
14
+ length = headers['Content-Length']
15
+ encoding = headers['Transfer-Encoding']
16
+ if length is not None:
17
+ length = int(length)
18
+ chunk_size = chunk_size or 16 * 1024**2
19
+ while length > 0:
20
+ rsize = min(length, chunk_size)
21
+
22
+ yield stream.read(rsize)
23
+
24
+ length -= rsize
25
+
26
+ elif encoding == 'chunked':
27
+ while True:
28
+ size = int(stream.readline().strip(), 16)
29
+ if size == 0:
30
+ break
31
+
32
+ yield stream.read(size)
33
+
34
+ while True:
35
+ ln = stream.readline().strip()
36
+ if not ln:
37
+ break
38
+ parts = ln.split(':', maxsplit=1)
39
+ if len(parts) == 2:
40
+ chunked_headers[part[0].strip()] = part[1].strip()
41
+
42
+ else:
43
+ raise RuntimeError(f'Unable to read data: {headers}')
44
+
45
+
46
+ class HandlerException(Exception):
47
+
48
+ def __init__(self, code, message, explain):
49
+ super().__init__()
50
+ self.code = code
51
+ self.message = message
52
+ self.explain = explain
53
+
54
+
55
+ class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
56
+
57
+ def _check_authorization(self, op, path):
58
+ hauth = self.headers['Authorization']
59
+ if hauth is None:
60
+ raise HandlerException(401, 'Unauthorized', f'Action not allowed on "{self.path}"\n')
61
+ if self._args.auth is None:
62
+ self.log_message(f'Client request needs authorization but server not configured ' \
63
+ f'for it: {op} {self.path}')
64
+ raise HandlerException(403, 'Forbidden', f'Action not allowed on "{self.path}"\n')
65
+
66
+ try:
67
+ auth_type, auth_config = [x.strip() for x in hauth.split(' ', maxsplit=1)]
68
+
69
+ auth_type = auth_type.lower()
70
+ if auth_type == 'bearer':
71
+ token = base64.b64decode(auth_config).decode()
72
+ if token != self._args.auth:
73
+ self.log_message(f'Client authorization invalid: {op} {self.path} {token}')
74
+ raise HandlerException(403, 'Forbidden', f'Action not allowed on "{self.path}"\n')
75
+ elif auth_type == 'basic':
76
+ creds = base64.b64decode(auth_config).decode()
77
+ user, passwd = creds.split(':', maxsplit=1)
78
+
79
+ # Very crude auth!
80
+ if passwd != self._args.auth:
81
+ self.log_message(f'Client authorization invalid: {op} {self.path} {user}:{passwd}')
82
+ raise HandlerException(403, 'Forbidden', f'Action not allowed on "{self.path}"\n')
83
+ else:
84
+ raise HandlerException(403, 'Forbidden', f'Authorization not supported: {hauth}\n')
85
+ except HandlerException:
86
+ raise
87
+ except Exception as ex:
88
+ raise HandlerException(500, 'Internal Server Error', f'Internal error: {ex}\n')
89
+
90
+ def do_OPTIONS(self):
91
+ self.send_response(200, 'OK')
92
+ self.send_header('Allow', 'GET,POST,PUT,OPTIONS,HEAD,DELETE')
93
+ self.send_header('Content-Length', '0')
94
+ self.end_headers()
95
+
96
+ def do_DELETE(self):
97
+ path = _sanitize_path(self.translate_path(self.path))
98
+ if path is None:
99
+ self.send_error(403,
100
+ message='Forbidden',
101
+ explain=f'Action not allowed on "{self.path}"\n')
102
+ else:
103
+ try:
104
+ self._check_authorization('DELETE', path)
105
+
106
+ os.remove(path)
107
+
108
+ self.send_response(200, 'OK')
109
+ self.send_header('Content-Length', '0')
110
+ self.end_headers()
111
+ except HandlerException as ex:
112
+ self.send_error(ex.code,
113
+ message=ex.message,
114
+ explain=ex.explain)
115
+ except OSError as ex:
116
+ self.send_error(500,
117
+ message='Internal Server Error',
118
+ explain=f'Internal error: {ex}\n')
119
+
120
+ def do_PUT(self):
121
+ path = _sanitize_path(self.translate_path(self.path))
122
+ if path is None:
123
+ self.send_error(403,
124
+ message='Forbidden',
125
+ explain=f'Action not allowed on "{self.path}"\n')
126
+ else:
127
+ try:
128
+ self._check_authorization('PUT', path)
129
+
130
+ os.makedirs(os.path.dirname(path), exist_ok=True)
131
+
132
+ chunked_headers = dict()
133
+ with open(path, 'wb') as f:
134
+ for data in _read_stream(self.headers, self.rfile, chunked_headers):
135
+ f.write(data)
136
+
137
+ self.send_response(201, 'Created')
138
+ self.end_headers()
139
+ except HandlerException as ex:
140
+ self.send_error(ex.code,
141
+ message=ex.message,
142
+ explain=ex.explain)
143
+ except Exception as ex:
144
+ self.send_error(500,
145
+ message='Internal Server Error',
146
+ explain=f'Internal error: {ex}\n')
147
+
148
+
149
+ class ClassWrapper:
150
+
151
+ PRIVATE_ATTRIBUTES = {'_cls', '_kwargs'}
152
+
153
+ def __init__(self, cls, **kwargs):
154
+ self._cls = cls
155
+ self._kwargs = kwargs
156
+
157
+ def __setattr__(self, name, value):
158
+ sdict = super().__getattribute__('__dict__')
159
+ sdict[name] = value
160
+ if name not in ClassWrapper.PRIVATE_ATTRIBUTES:
161
+ sdict['_kwargs'][name] = value
162
+
163
+ def __call__(self, *args, **kwargs):
164
+ obj = self._cls.__new__(self._cls, *args, **kwargs)
165
+ # Add ctor supplied data and data planted by the http.server.test() API
166
+ # (talking about bad API design).
167
+ obj.__dict__.update(self._kwargs)
168
+ obj.__init__(*args, **kwargs)
169
+
170
+ return obj
171
+
172
+
173
+ if __name__ == '__main__':
174
+ parser = argparse.ArgumentParser(description='Simple HTTP Server For Testing')
175
+ parser.add_argument('--bind', default='0.0.0.0',
176
+ help='Specify alternate bind address')
177
+ parser.add_argument('--port', type=int, default=8000,
178
+ help='Specify alternate port')
179
+ parser.add_argument('--protocol', default='HTTP/1.1',
180
+ help='Conform to this HTTP version')
181
+ parser.add_argument('--auth',
182
+ help='The authentication to be used when performing HTTP write operations')
183
+
184
+ args = parser.parse_args()
185
+
186
+ # Wrap the handler and server classes, to allow adding the args (and also because
187
+ # the http.server.test() API plants data inside the class global namespace!
188
+ req_handler = ClassWrapper(HTTPRequestHandler, _args=args)
189
+ server_class = ClassWrapper(http.server.ThreadingHTTPServer)
190
+
191
+ http.server.test(HandlerClass=req_handler,
192
+ ServerClass=server_class,
193
+ port=args.port,
194
+ bind=args.bind,
195
+ protocol=args.protocol)
196
+
@@ -0,0 +1,143 @@
1
+ import collections
2
+ import os
3
+ import re
4
+ import requests
5
+ import time
6
+
7
+
8
+ ACCEPT_RANGES = 'Accept-Ranges'
9
+ AUTHORIZATION = 'Authorization'
10
+ CONTENT_LENGTH = 'Content-Length'
11
+ CONTENT_TYPE = 'Content-Type'
12
+ CONTENT_ENCODING = 'Content-Encoding'
13
+ LAST_MODIFIED = 'Last-Modified'
14
+ XLINKED_SIZE = 'X-Linked-Size'
15
+ XLINKED_ETAG = 'X-Linked-ETag'
16
+ ETAG = 'ETag'
17
+ RANGE = 'Range'
18
+ CONTENT_RANGE = 'Content-Range'
19
+
20
+
21
+ def support_ranges(headers):
22
+ return 'bytes' in headers.get(ACCEPT_RANGES, '')
23
+
24
+
25
+ def content_length(headers, defval=None):
26
+ if (length := headers.get(XLINKED_SIZE)) is not None:
27
+ return int(length)
28
+ if (length := headers.get(CONTENT_LENGTH)) is not None:
29
+ return int(length)
30
+
31
+ return defval
32
+
33
+
34
+ def etag(headers, defval=None):
35
+ if (etag_value := headers.get(XLINKED_ETAG)) is not None:
36
+ return etag_value.strip('"\'')
37
+ if (etag_value := headers.get(ETAG)) is not None:
38
+ return etag_value.strip('"\'')
39
+
40
+ return defval
41
+
42
+
43
+ def last_modified(headers, defval=None):
44
+ if (mtime := headers.get(LAST_MODIFIED)) is not None:
45
+ return date_to_epoch(mtime)
46
+
47
+ return defval
48
+
49
+
50
+ def add_range(headers, start, end):
51
+ headers[RANGE] = f'bytes={start}-{end - 1}'
52
+
53
+ return headers
54
+
55
+
56
+ Range = collections.namedtuple('Range', 'start, stop, length')
57
+
58
+ def range(headers):
59
+ hrange = headers.get(CONTENT_RANGE)
60
+ if hrange is None:
61
+ if (length := content_length(headers)) is not None:
62
+ return Range(start=0, stop=length - 1, length=length)
63
+ else:
64
+ m = re.match(r'bytes\s+(\d+)\-(\d+)(/(\d+))?', hrange)
65
+ if m:
66
+ hlength = m.group(4)
67
+ length = int(hlength) if hlength else None
68
+
69
+ return Range(start=int(m.group(1)), stop=int(m.group(2)), length=length)
70
+
71
+
72
+ def range_data(start, stop, headers, data):
73
+ hrange = range(headers)
74
+ if hrange is not None:
75
+ dstart = start - hrange.start
76
+ size = min(stop - start, hrange.stop - hrange.start) + 1
77
+ dstop = dstart + size - 1
78
+
79
+ if dstart != start or dstop != stop:
80
+ return memoryview(data)[dstart: dstop + 1]
81
+
82
+ return data
83
+
84
+
85
+ _HTTP_DATE_FMT ='%a, %d %b %Y %H:%M:%S %Z'
86
+
87
+ def date_to_epoch(http_date):
88
+ htime = time.strptime(http_date, _HTTP_DATE_FMT)
89
+
90
+ return time.mktime(htime)
91
+
92
+
93
+ def epoch_to_date(epoch_time=None):
94
+ return time.strftime(_HTTP_DATE_FMT, time.gmtime(epoch_time or time.time()))
95
+
96
+
97
+ def filter_request_args(kwargs, key=None):
98
+ req_kwargs = kwargs.get(key or 'requests')
99
+ if req_kwargs is None:
100
+ req_kwargs = dict()
101
+ for arg in ('headers', 'timeout', 'auth', 'cookies', 'allow_redirects',
102
+ 'proxies', 'verify', 'cert'):
103
+ argv = kwargs.get(arg)
104
+ if argv is not None:
105
+ req_kwargs[arg] = argv
106
+
107
+ return req_kwargs
108
+
109
+
110
+ def info(url, mod=None, headers=None, **kwargs):
111
+ mod = mod or requests
112
+ req_headers = headers.copy() if headers else dict()
113
+
114
+ add_range(req_headers, 0, 1024)
115
+
116
+ try:
117
+ resp = mod.get(url, headers=req_headers, **kwargs)
118
+ resp.raise_for_status()
119
+
120
+ hrange = range(resp.headers)
121
+ if hrange is not None and hrange.length is not None:
122
+ resp.headers[CONTENT_LENGTH] = hrange.length
123
+ resp.headers[ACCEPT_RANGES] = 'bytes'
124
+ else:
125
+ resp = None
126
+ except requests.exceptions.HTTPError:
127
+ resp = None
128
+
129
+ if resp is None:
130
+ resp = mod.head(url, headers=headers, **kwargs)
131
+ resp.raise_for_status()
132
+
133
+ return resp
134
+
135
+
136
+ def get(url, mod=None, **kwargs):
137
+ mod = mod or requests
138
+
139
+ resp = mod.get(url, **kwargs)
140
+ resp.raise_for_status()
141
+
142
+ return resp.content
143
+
@@ -0,0 +1,20 @@
1
+ import io
2
+
3
+ import PIL.Image as Image
4
+
5
+ from . import http_utils as hu
6
+
7
+
8
+ def from_bytes(data, convert=None):
9
+ try:
10
+ img = Image.open(io.BytesIO(data))
11
+ except Exception as ex:
12
+ ex.add_note(f'Unable to load image: data={data[: 16]}...')
13
+ raise
14
+
15
+ return img if convert is None else img.convert(convert)
16
+
17
+
18
+ def from_url(url, convert=None, **kwargs):
19
+ return from_bytes(hu.get(url, **kwargs), convert=convert)
20
+
@@ -0,0 +1,20 @@
1
+
2
+ class InfixOp:
3
+ def __init__(self, opfn):
4
+ self._opfn = opfn
5
+
6
+ def __ror__(self, lhs):
7
+ return InfixOp(lambda x, self=self, lhs=lhs: self._opfn(lhs, x))
8
+
9
+ def __or__(self, rhs):
10
+ return self._opfn(rhs)
11
+
12
+ def __rlshift__(self, lhs):
13
+ return InfixOp(lambda x, self=self, lhs=lhs: self._opfn(lhs, x))
14
+
15
+ def __rshift__(self, rhs):
16
+ return self._opfn(rhs)
17
+
18
+ def __call__(self, value1, value2):
19
+ return self._opfn(value1, value2)
20
+
@@ -0,0 +1,205 @@
1
+ import inspect
2
+ import itertools
3
+ import sys
4
+ import types
5
+
6
+ from . import assert_checks as tas
7
+ from . import traceback as tb
8
+
9
+
10
+ _NONE = object()
11
+
12
+
13
+ def classof(obj):
14
+ return obj if inspect.isclass(obj) else getattr(obj, '__class__', None)
15
+
16
+
17
+ def moduleof(obj):
18
+ cls = classof(obj)
19
+
20
+ return getattr(cls, '__module__', None) if cls is not None else None
21
+
22
+
23
+ def cname(obj):
24
+ cls = classof(obj)
25
+
26
+ return cls.__name__ if cls is not None else None
27
+
28
+
29
+ def func_name(func):
30
+ fname = getattr(func, '__name__', None)
31
+
32
+ return fname if fname is not None else cname(func)
33
+
34
+
35
+ _BUILTIN_NAMES = {'__builtin__', 'builtins'}
36
+
37
+ def _qual_name(obj, builtin_strip=False):
38
+ module = getattr(obj, '__module__', None)
39
+ name = getattr(obj, '__qualname__', None)
40
+ if name is None:
41
+ name = getattr(obj, '__name__', None)
42
+ tas.check_is_not_none(name, msg=f'Unable to reference name: {obj}')
43
+ if module is not None and not (builtin_strip and module in _BUILTIN_NAMES):
44
+ name = module + '.' + name
45
+
46
+ return name
47
+
48
+
49
+ def qual_name(obj, builtin_strip=False):
50
+ if (inspect.isclass(obj) or inspect.isfunction(obj) or
51
+ inspect.ismethod(obj) or inspect.ismodule(obj)):
52
+ ref = obj
53
+ else:
54
+ ref = obj.__class__
55
+
56
+ return _qual_name(ref, builtin_strip=builtin_strip)
57
+
58
+
59
+ def qual_mro(obj, builtin_strip=False):
60
+ cls = obj if inspect.isclass(obj) else obj.__class__
61
+
62
+ for scls in cls.__mro__:
63
+ yield _qual_name(scls, builtin_strip=builtin_strip)
64
+
65
+
66
+ def is_subclass(cls, cls_group):
67
+ return inspect.isclass(cls) and issubclass(cls, cls_group)
68
+
69
+
70
+ def _fn_lookup(frame, name):
71
+ xns, xname = None, name
72
+ while True:
73
+ dpos = xname.find('.')
74
+ if dpos > 0:
75
+ cname, fname = xname[: dpos], xname[dpos + 1:]
76
+ if cname == '<locals>':
77
+ code = getattr(xns, '__code__', None)
78
+ if code is not None:
79
+ for cv in code.co_consts:
80
+ if inspect.iscode(cv) and cv.co_name == fname:
81
+ return types.FunctionType(cv, frame.f_globals, fname)
82
+
83
+ return None
84
+ else:
85
+ xns = frame.f_globals[cname] if xns is None else getattr(xns, cname)
86
+ xname = fname
87
+ else:
88
+ xns = frame.f_globals[xname] if xns is None else getattr(xns, xname)
89
+ break
90
+
91
+ return xns
92
+
93
+
94
+ def get_caller_function(back=0, frame=None):
95
+ if frame is None:
96
+ frame = tb.get_frame(back + 1)
97
+
98
+ return _fn_lookup(frame, frame.f_code.co_qualname)
99
+
100
+
101
+ def current_module():
102
+ return inspect.getmodule(tb.get_frame(1))
103
+
104
+
105
+ def fetch_args(func, locs, input_args=()):
106
+ sig = inspect.signature(func)
107
+
108
+ def args_append(args, n):
109
+ if len(args) < len(input_args):
110
+ args.append(input_args[len(args)])
111
+ else:
112
+ pv = locs.get(n, _NONE)
113
+ if pv is not _NONE:
114
+ args.append(pv)
115
+ elif args:
116
+ alog.xraise(RuntimeError, f'Missing argument: {n}')
117
+
118
+ def kwargs_assign(kwargs, n, p):
119
+ pv = locs.get(n, _NONE)
120
+ if pv is _NONE or (pv is None and p.default is not inspect.Signature.empty):
121
+ pv = p.default
122
+ if pv is not inspect.Signature.empty:
123
+ kwargs[n] = pv
124
+
125
+ args, kwargs = [], dict()
126
+ for n, p in sig.parameters.items():
127
+ if p.kind == p.POSITIONAL_ONLY:
128
+ args_append(args, n)
129
+ elif p.kind == p.POSITIONAL_OR_KEYWORD:
130
+ if p.default is inspect.Signature.empty:
131
+ args_append(args, n)
132
+ else:
133
+ kwargs_assign(kwargs, n, p)
134
+ else:
135
+ kwargs_assign(kwargs, n, p)
136
+
137
+ return args, kwargs
138
+
139
+
140
+ def fetch_call(func, locs, input_args=()):
141
+ args, kwargs = fetch_args(func, locs, input_args=input_args)
142
+
143
+ return func(*args, **kwargs)
144
+
145
+
146
+ def get_fn_kwargs(args, func, prefix=None, roffset=None):
147
+ aspec = inspect.getfullargspec(func)
148
+
149
+ sdefaults = aspec.defaults or ()
150
+ sargs = aspec.args or ()
151
+ ndelta = len(sargs) - len(sdefaults)
152
+
153
+ fnargs = dict()
154
+ for i, an in enumerate(sargs):
155
+ if i != 0 or an != 'self':
156
+ nn = f'{prefix}.{an}' if prefix else an
157
+ di = i - ndelta
158
+ if di >= 0:
159
+ fnargs[an] = args.get(nn, sdefaults[di])
160
+ elif roffset is not None and i >= roffset:
161
+ aval = args.get(nn, _NONE)
162
+ tas.check(aval is not _NONE,
163
+ msg=f'The "{an}" argument must be present as "{nn}": {args}')
164
+ fnargs[an] = aval
165
+
166
+ if aspec.kwonlyargs:
167
+ for an in aspec.kwonlyargs:
168
+ nn = f'{prefix}.{an}' if prefix else an
169
+ aval = args.get(nn, aspec.kwonlydefaults.get(an, inspect.Signature.empty))
170
+ if aval is not inspect.Signature.empty:
171
+ fnargs[an] = aval
172
+
173
+ return fnargs
174
+
175
+
176
+ def get_defaulted_params(func):
177
+ sig = inspect.signature(func)
178
+
179
+ return tuple(p for p in sig.parameters.values()
180
+ if p.default is not inspect.Signature.empty)
181
+
182
+
183
+ def parent_locals(level=0):
184
+ frame = tb.get_frame(level + 2)
185
+
186
+ return frame.f_locals
187
+
188
+
189
+ def parent_globals(level=0):
190
+ frame = tb.get_frame(level + 2)
191
+
192
+ return frame.f_globals
193
+
194
+
195
+ def parent_coords(level=0):
196
+ frame = tb.get_frame(level + 2)
197
+
198
+ return frame.f_code.co_filename, frame.f_lineno
199
+
200
+
201
+ def class_slots(cls):
202
+ slots = itertools.chain.from_iterable(getattr(mcls, '__slots__', []) for mcls in cls.__mro__)
203
+
204
+ return tuple(slots)
205
+
@@ -0,0 +1,21 @@
1
+ import functools
2
+ import os
3
+
4
+
5
+ class IOStream:
6
+
7
+ def __init__(self, fd):
8
+ if (wfn := getattr(fd, 'write', None)) is not None:
9
+ self.write = wfn
10
+ elif (wfn := getattr(fd, 'send', None)) is not None:
11
+ self.write = wfn
12
+ else:
13
+ self.write = functools.partial(os.write, fd)
14
+
15
+ if (rfn := getattr(fd, 'read', None)) is not None:
16
+ self.read = rfn
17
+ elif (rfn := getattr(fd, 'recv', None)) is not None:
18
+ self.read = rfn
19
+ else:
20
+ self.read = functools.partial(os.read, fd)
21
+