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.
- py_misc_utils/__init__.py +0 -0
- py_misc_utils/abs_timeout.py +12 -0
- py_misc_utils/alog.py +311 -0
- py_misc_utils/app_main.py +179 -0
- py_misc_utils/archive_streamer.py +112 -0
- py_misc_utils/assert_checks.py +118 -0
- py_misc_utils/ast_utils.py +121 -0
- py_misc_utils/async_manager.py +189 -0
- py_misc_utils/break_control.py +63 -0
- py_misc_utils/buffered_iterator.py +35 -0
- py_misc_utils/cached_file.py +507 -0
- py_misc_utils/call_limiter.py +26 -0
- py_misc_utils/call_result_selector.py +13 -0
- py_misc_utils/cleanups.py +85 -0
- py_misc_utils/cmd.py +97 -0
- py_misc_utils/compression.py +116 -0
- py_misc_utils/cond_waiter.py +13 -0
- py_misc_utils/context_base.py +18 -0
- py_misc_utils/context_managers.py +67 -0
- py_misc_utils/core_utils.py +577 -0
- py_misc_utils/daemon_process.py +252 -0
- py_misc_utils/data_cache.py +46 -0
- py_misc_utils/date_utils.py +90 -0
- py_misc_utils/debug.py +24 -0
- py_misc_utils/dyn_modules.py +50 -0
- py_misc_utils/dynamod.py +103 -0
- py_misc_utils/env_config.py +35 -0
- py_misc_utils/executor.py +239 -0
- py_misc_utils/file_overwrite.py +29 -0
- py_misc_utils/fin_wrap.py +77 -0
- py_misc_utils/fp_utils.py +47 -0
- py_misc_utils/fs/__init__.py +0 -0
- py_misc_utils/fs/file_fs.py +127 -0
- py_misc_utils/fs/ftp_fs.py +242 -0
- py_misc_utils/fs/gcs_fs.py +196 -0
- py_misc_utils/fs/http_fs.py +241 -0
- py_misc_utils/fs/s3_fs.py +417 -0
- py_misc_utils/fs_base.py +133 -0
- py_misc_utils/fs_utils.py +207 -0
- py_misc_utils/gcs_fs.py +169 -0
- py_misc_utils/gen_indices.py +54 -0
- py_misc_utils/gfs.py +371 -0
- py_misc_utils/git_repo.py +77 -0
- py_misc_utils/global_namespace.py +110 -0
- py_misc_utils/http_async_fetcher.py +139 -0
- py_misc_utils/http_server.py +196 -0
- py_misc_utils/http_utils.py +143 -0
- py_misc_utils/img_utils.py +20 -0
- py_misc_utils/infix_op.py +20 -0
- py_misc_utils/inspect_utils.py +205 -0
- py_misc_utils/iostream.py +21 -0
- py_misc_utils/iter_file.py +117 -0
- py_misc_utils/key_wrap.py +46 -0
- py_misc_utils/lazy_import.py +25 -0
- py_misc_utils/lockfile.py +164 -0
- py_misc_utils/mem_size.py +64 -0
- py_misc_utils/mirror_from.py +72 -0
- py_misc_utils/mmap.py +16 -0
- py_misc_utils/module_utils.py +196 -0
- py_misc_utils/moving_average.py +19 -0
- py_misc_utils/msgpack_streamer.py +26 -0
- py_misc_utils/multi_wait.py +24 -0
- py_misc_utils/multiprocessing.py +102 -0
- py_misc_utils/named_array.py +224 -0
- py_misc_utils/no_break.py +46 -0
- py_misc_utils/no_except.py +32 -0
- py_misc_utils/np_ml_framework.py +184 -0
- py_misc_utils/np_utils.py +346 -0
- py_misc_utils/ntuple_utils.py +38 -0
- py_misc_utils/num_utils.py +54 -0
- py_misc_utils/obj.py +73 -0
- py_misc_utils/object_cache.py +100 -0
- py_misc_utils/object_tracker.py +88 -0
- py_misc_utils/ordered_set.py +71 -0
- py_misc_utils/osfd.py +27 -0
- py_misc_utils/packet.py +22 -0
- py_misc_utils/parquet_streamer.py +69 -0
- py_misc_utils/pd_utils.py +254 -0
- py_misc_utils/periodic_task.py +61 -0
- py_misc_utils/pickle_wrap.py +121 -0
- py_misc_utils/pipeline.py +98 -0
- py_misc_utils/remap_pickle.py +50 -0
- py_misc_utils/resource_manager.py +155 -0
- py_misc_utils/rnd_utils.py +56 -0
- py_misc_utils/run_once.py +19 -0
- py_misc_utils/scheduler.py +135 -0
- py_misc_utils/select_params.py +300 -0
- py_misc_utils/signal.py +141 -0
- py_misc_utils/skl_utils.py +270 -0
- py_misc_utils/split.py +147 -0
- py_misc_utils/state.py +53 -0
- py_misc_utils/std_module.py +56 -0
- py_misc_utils/stream_dataframe.py +176 -0
- py_misc_utils/streamed_file.py +144 -0
- py_misc_utils/tempdir.py +79 -0
- py_misc_utils/template_replace.py +51 -0
- py_misc_utils/tensor_stream.py +269 -0
- py_misc_utils/thread_context.py +33 -0
- py_misc_utils/throttle.py +30 -0
- py_misc_utils/time_trigger.py +18 -0
- py_misc_utils/timegen.py +11 -0
- py_misc_utils/traceback.py +49 -0
- py_misc_utils/tracking_executor.py +91 -0
- py_misc_utils/transform_array.py +42 -0
- py_misc_utils/uncompress.py +35 -0
- py_misc_utils/url_fetcher.py +157 -0
- py_misc_utils/utils.py +538 -0
- py_misc_utils/varint.py +50 -0
- py_misc_utils/virt_array.py +52 -0
- py_misc_utils/weak_call.py +33 -0
- py_misc_utils/work_results.py +100 -0
- py_misc_utils/writeback_file.py +43 -0
- python_misc_utils-0.2.dist-info/METADATA +36 -0
- python_misc_utils-0.2.dist-info/RECORD +117 -0
- python_misc_utils-0.2.dist-info/WHEEL +5 -0
- python_misc_utils-0.2.dist-info/licenses/LICENSE +13 -0
- 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
|
+
|