omdev 0.0.0.dev404__py3-none-any.whl → 0.0.0.dev406__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.
- omdev/.manifests.json +138 -138
- omdev/cli/main.py +20 -23
- omdev/cli/types.py +5 -5
- omdev/manifests/_dumping.py +3 -3
- omdev/manifests/building.py +13 -7
- omdev/manifests/dumping.py +3 -3
- omdev/precheck/manifests.py +1 -1
- omdev/py/attrdocs.py +3 -3
- omdev/py/bracepy.py +3 -3
- omdev/py/classdot.py +3 -3
- omdev/py/findimports.py +3 -3
- omdev/py/scripts/execstat.py +18 -10
- omdev/py/scripts/importtrace.py +3 -3
- omdev/py/srcheaders.py +3 -3
- omdev/scripts/ci.py +466 -461
- omdev/scripts/slowcat.py +3 -3
- omdev/scripts/tmpexec.py +3 -3
- omdev/tools/git/messages.py +2 -6
- omdev/tools/mkenv.py +3 -3
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/METADATA +2 -2
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/RECORD +25 -25
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/WHEEL +0 -0
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/entry_points.txt +0 -0
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/licenses/LICENSE +0 -0
- {omdev-0.0.0.dev404.dist-info → omdev-0.0.0.dev406.dist-info}/top_level.txt +0 -0
omdev/scripts/ci.py
CHANGED
@@ -99,6 +99,10 @@ CheckOnRaiseFn = ta.Callable[[Exception], None] # ta.TypeAlias
|
|
99
99
|
CheckExceptionFactory = ta.Callable[..., Exception] # ta.TypeAlias
|
100
100
|
CheckArgsRenderer = ta.Callable[..., ta.Optional[str]] # ta.TypeAlias
|
101
101
|
|
102
|
+
# ../../omlish/lite/contextmanagers.py
|
103
|
+
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
104
|
+
AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
105
|
+
|
102
106
|
# ../../omlish/lite/maybes.py
|
103
107
|
U = ta.TypeVar('U')
|
104
108
|
|
@@ -121,10 +125,6 @@ AwaitableT = ta.TypeVar('AwaitableT', bound=ta.Awaitable)
|
|
121
125
|
# ../../omlish/http/parsing.py
|
122
126
|
HttpHeaders = http.client.HTTPMessage # ta.TypeAlias
|
123
127
|
|
124
|
-
# ../../omlish/lite/contextmanagers.py
|
125
|
-
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
126
|
-
AsyncExitStackedT = ta.TypeVar('AsyncExitStackedT', bound='AsyncExitStacked')
|
127
|
-
|
128
128
|
# ../../omlish/lite/inject.py
|
129
129
|
InjectorKeyCls = ta.Union[type, ta.NewType]
|
130
130
|
InjectorProviderFn = ta.Callable[['Injector'], ta.Any]
|
@@ -1026,6 +1026,201 @@ class Checks:
|
|
1026
1026
|
check = Checks()
|
1027
1027
|
|
1028
1028
|
|
1029
|
+
########################################
|
1030
|
+
# ../../../omlish/lite/contextmanagers.py
|
1031
|
+
|
1032
|
+
|
1033
|
+
##
|
1034
|
+
|
1035
|
+
|
1036
|
+
class ExitStacked:
|
1037
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1038
|
+
super().__init_subclass__(**kwargs)
|
1039
|
+
|
1040
|
+
for a in ('__enter__', '__exit__'):
|
1041
|
+
for b in cls.__bases__:
|
1042
|
+
if b is ExitStacked:
|
1043
|
+
continue
|
1044
|
+
try:
|
1045
|
+
fn = getattr(b, a)
|
1046
|
+
except AttributeError:
|
1047
|
+
pass
|
1048
|
+
else:
|
1049
|
+
if fn is not getattr(ExitStacked, a):
|
1050
|
+
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
1051
|
+
|
1052
|
+
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
1053
|
+
|
1054
|
+
@contextlib.contextmanager
|
1055
|
+
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
1056
|
+
"""
|
1057
|
+
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
1058
|
+
things like wrapping __enter__ in a lock.
|
1059
|
+
"""
|
1060
|
+
|
1061
|
+
yield
|
1062
|
+
|
1063
|
+
@ta.final
|
1064
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
1065
|
+
"""
|
1066
|
+
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
1067
|
+
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
1068
|
+
"""
|
1069
|
+
|
1070
|
+
with self._exit_stacked_init_wrapper():
|
1071
|
+
if self._exit_stack is not None:
|
1072
|
+
raise RuntimeError
|
1073
|
+
es = self._exit_stack = contextlib.ExitStack()
|
1074
|
+
es.__enter__()
|
1075
|
+
try:
|
1076
|
+
self._enter_contexts()
|
1077
|
+
except Exception: # noqa
|
1078
|
+
es.__exit__(*sys.exc_info())
|
1079
|
+
raise
|
1080
|
+
return self
|
1081
|
+
|
1082
|
+
@ta.final
|
1083
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
1084
|
+
if (es := self._exit_stack) is None:
|
1085
|
+
return None
|
1086
|
+
try:
|
1087
|
+
self._exit_contexts()
|
1088
|
+
except Exception: # noqa
|
1089
|
+
es.__exit__(*sys.exc_info())
|
1090
|
+
raise
|
1091
|
+
return es.__exit__(exc_type, exc_val, exc_tb)
|
1092
|
+
|
1093
|
+
def _enter_contexts(self) -> None:
|
1094
|
+
pass
|
1095
|
+
|
1096
|
+
def _exit_contexts(self) -> None:
|
1097
|
+
pass
|
1098
|
+
|
1099
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1100
|
+
if (es := self._exit_stack) is None:
|
1101
|
+
raise RuntimeError
|
1102
|
+
return es.enter_context(cm)
|
1103
|
+
|
1104
|
+
|
1105
|
+
class AsyncExitStacked:
|
1106
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
1107
|
+
super().__init_subclass__(**kwargs)
|
1108
|
+
|
1109
|
+
for a in ('__aenter__', '__aexit__'):
|
1110
|
+
for b in cls.__bases__:
|
1111
|
+
if b is AsyncExitStacked:
|
1112
|
+
continue
|
1113
|
+
try:
|
1114
|
+
fn = getattr(b, a)
|
1115
|
+
except AttributeError:
|
1116
|
+
pass
|
1117
|
+
else:
|
1118
|
+
if fn is not getattr(AsyncExitStacked, a):
|
1119
|
+
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
1120
|
+
|
1121
|
+
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
1122
|
+
|
1123
|
+
@contextlib.asynccontextmanager
|
1124
|
+
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
1125
|
+
yield
|
1126
|
+
|
1127
|
+
@ta.final
|
1128
|
+
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
1129
|
+
async with self._async_exit_stacked_init_wrapper():
|
1130
|
+
if self._exit_stack is not None:
|
1131
|
+
raise RuntimeError
|
1132
|
+
es = self._exit_stack = contextlib.AsyncExitStack()
|
1133
|
+
await es.__aenter__()
|
1134
|
+
try:
|
1135
|
+
await self._async_enter_contexts()
|
1136
|
+
except Exception: # noqa
|
1137
|
+
await es.__aexit__(*sys.exc_info())
|
1138
|
+
raise
|
1139
|
+
return self
|
1140
|
+
|
1141
|
+
@ta.final
|
1142
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
1143
|
+
if (es := self._exit_stack) is None:
|
1144
|
+
return None
|
1145
|
+
try:
|
1146
|
+
await self._async_exit_contexts()
|
1147
|
+
except Exception: # noqa
|
1148
|
+
await es.__aexit__(*sys.exc_info())
|
1149
|
+
raise
|
1150
|
+
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
1151
|
+
|
1152
|
+
async def _async_enter_contexts(self) -> None:
|
1153
|
+
pass
|
1154
|
+
|
1155
|
+
async def _async_exit_contexts(self) -> None:
|
1156
|
+
pass
|
1157
|
+
|
1158
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
1159
|
+
if (es := self._exit_stack) is None:
|
1160
|
+
raise RuntimeError
|
1161
|
+
return es.enter_context(cm)
|
1162
|
+
|
1163
|
+
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
1164
|
+
if (es := self._exit_stack) is None:
|
1165
|
+
raise RuntimeError
|
1166
|
+
return await es.enter_async_context(cm)
|
1167
|
+
|
1168
|
+
|
1169
|
+
##
|
1170
|
+
|
1171
|
+
|
1172
|
+
@contextlib.contextmanager
|
1173
|
+
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
1174
|
+
if args or kwargs:
|
1175
|
+
fn = functools.partial(fn, *args, **kwargs)
|
1176
|
+
try:
|
1177
|
+
yield fn
|
1178
|
+
finally:
|
1179
|
+
fn()
|
1180
|
+
|
1181
|
+
|
1182
|
+
@contextlib.asynccontextmanager
|
1183
|
+
async def adefer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
|
1184
|
+
try:
|
1185
|
+
yield fn
|
1186
|
+
finally:
|
1187
|
+
await fn
|
1188
|
+
|
1189
|
+
|
1190
|
+
##
|
1191
|
+
|
1192
|
+
|
1193
|
+
@contextlib.contextmanager
|
1194
|
+
def attr_setting(obj, attr, val, *, default=None): # noqa
|
1195
|
+
not_set = object()
|
1196
|
+
orig = getattr(obj, attr, not_set)
|
1197
|
+
try:
|
1198
|
+
setattr(obj, attr, val)
|
1199
|
+
if orig is not not_set:
|
1200
|
+
yield orig
|
1201
|
+
else:
|
1202
|
+
yield default
|
1203
|
+
finally:
|
1204
|
+
if orig is not_set:
|
1205
|
+
delattr(obj, attr)
|
1206
|
+
else:
|
1207
|
+
setattr(obj, attr, orig)
|
1208
|
+
|
1209
|
+
|
1210
|
+
##
|
1211
|
+
|
1212
|
+
|
1213
|
+
class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
1214
|
+
def __init__(self, thing):
|
1215
|
+
self.thing = thing
|
1216
|
+
|
1217
|
+
async def __aenter__(self):
|
1218
|
+
return self.thing
|
1219
|
+
|
1220
|
+
async def __aexit__(self, *exc_info):
|
1221
|
+
await self.thing.aclose()
|
1222
|
+
|
1223
|
+
|
1029
1224
|
########################################
|
1030
1225
|
# ../../../omlish/lite/dataclasses.py
|
1031
1226
|
|
@@ -2721,53 +2916,251 @@ class OciDataRefInfo:
|
|
2721
2916
|
|
2722
2917
|
|
2723
2918
|
########################################
|
2724
|
-
#
|
2725
|
-
"""
|
2726
|
-
FIXME:
|
2727
|
-
- exit_on_error lol
|
2728
|
-
|
2729
|
-
TODO:
|
2730
|
-
- default command
|
2731
|
-
- auto match all underscores to hyphens
|
2732
|
-
- pre-run, post-run hooks
|
2733
|
-
- exitstack?
|
2734
|
-
- suggestion - difflib.get_close_matches
|
2735
|
-
"""
|
2919
|
+
# ../../oci/pack/unpacking.py
|
2736
2920
|
|
2737
2921
|
|
2738
2922
|
##
|
2739
2923
|
|
2740
2924
|
|
2741
|
-
|
2742
|
-
|
2743
|
-
|
2744
|
-
|
2745
|
-
|
2746
|
-
|
2747
|
-
|
2748
|
-
if instance is None:
|
2749
|
-
return self
|
2750
|
-
return getattr(instance.args, self.dest) # type: ignore
|
2751
|
-
|
2925
|
+
class OciLayerUnpacker(ExitStacked):
|
2926
|
+
def __init__(
|
2927
|
+
self,
|
2928
|
+
input_files: ta.Sequence[ta.Union[str, tarfile.TarFile]],
|
2929
|
+
output_file_path: str,
|
2930
|
+
) -> None:
|
2931
|
+
super().__init__()
|
2752
2932
|
|
2753
|
-
|
2754
|
-
|
2933
|
+
self._input_files = list(input_files)
|
2934
|
+
self._output_file_path = output_file_path
|
2755
2935
|
|
2936
|
+
#
|
2756
2937
|
|
2757
|
-
|
2758
|
-
|
2938
|
+
@contextlib.contextmanager
|
2939
|
+
def _open_input_file(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Iterator[tarfile.TarFile]:
|
2940
|
+
if isinstance(input_file, tarfile.TarFile):
|
2941
|
+
yield input_file
|
2759
2942
|
|
2943
|
+
elif isinstance(input_file, str):
|
2944
|
+
with tarfile.open(input_file) as tar_file:
|
2945
|
+
yield tar_file
|
2760
2946
|
|
2761
|
-
|
2947
|
+
else:
|
2948
|
+
raise TypeError(input_file)
|
2762
2949
|
|
2950
|
+
#
|
2763
2951
|
|
2764
|
-
|
2765
|
-
|
2766
|
-
|
2767
|
-
fn: ArgparseCmdFn
|
2768
|
-
args: ta.Sequence[ArgparseArg] = () # noqa
|
2952
|
+
class _Entry(ta.NamedTuple):
|
2953
|
+
file: ta.Union[str, tarfile.TarFile]
|
2954
|
+
info: tarfile.TarInfo
|
2769
2955
|
|
2770
|
-
|
2956
|
+
def _build_input_file_sorted_entries(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Sequence[_Entry]:
|
2957
|
+
dct: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
2958
|
+
|
2959
|
+
with self._open_input_file(input_file) as input_tar_file:
|
2960
|
+
for info in input_tar_file.getmembers():
|
2961
|
+
check.not_in(info.name, dct)
|
2962
|
+
dct[info.name] = self._Entry(
|
2963
|
+
file=input_file,
|
2964
|
+
info=info,
|
2965
|
+
)
|
2966
|
+
|
2967
|
+
return sorted(dct.values(), key=lambda entry: entry.info.name)
|
2968
|
+
|
2969
|
+
@cached_nullary
|
2970
|
+
def _entries_by_name(self) -> ta.Mapping[str, _Entry]:
|
2971
|
+
root: dict = {}
|
2972
|
+
|
2973
|
+
def find_dir(dir_name: str) -> dict: # noqa
|
2974
|
+
if dir_name:
|
2975
|
+
dir_parts = dir_name.split('/')
|
2976
|
+
else:
|
2977
|
+
dir_parts = []
|
2978
|
+
|
2979
|
+
cur = root # noqa
|
2980
|
+
for dir_part in dir_parts:
|
2981
|
+
cur = cur[dir_part] # noqa
|
2982
|
+
|
2983
|
+
return check.isinstance(cur, dict)
|
2984
|
+
|
2985
|
+
#
|
2986
|
+
|
2987
|
+
for input_file in self._input_files:
|
2988
|
+
sorted_entries = self._build_input_file_sorted_entries(input_file)
|
2989
|
+
|
2990
|
+
wh_names = set()
|
2991
|
+
wh_opaques = set()
|
2992
|
+
|
2993
|
+
#
|
2994
|
+
|
2995
|
+
for entry in sorted_entries:
|
2996
|
+
info = entry.info
|
2997
|
+
name = check.non_empty_str(info.name)
|
2998
|
+
base_name = os.path.basename(name)
|
2999
|
+
dir_name = os.path.dirname(name)
|
3000
|
+
|
3001
|
+
if base_name == '.wh..wh..opq':
|
3002
|
+
wh_opaques.add(dir_name)
|
3003
|
+
continue
|
3004
|
+
|
3005
|
+
if base_name.startswith('.wh.'):
|
3006
|
+
wh_base_name = os.path.basename(base_name[4:])
|
3007
|
+
wh_name = os.path.join(dir_name, wh_base_name)
|
3008
|
+
wh_names.add(wh_name)
|
3009
|
+
continue
|
3010
|
+
|
3011
|
+
cur = find_dir(dir_name)
|
3012
|
+
|
3013
|
+
if info.type == tarfile.DIRTYPE:
|
3014
|
+
try:
|
3015
|
+
ex = cur[base_name]
|
3016
|
+
except KeyError:
|
3017
|
+
cur[base_name] = {'': entry}
|
3018
|
+
else:
|
3019
|
+
ex[''] = entry
|
3020
|
+
|
3021
|
+
else:
|
3022
|
+
cur[base_name] = entry
|
3023
|
+
|
3024
|
+
#
|
3025
|
+
|
3026
|
+
for wh_name in reversed(sorted(wh_names)): # noqa
|
3027
|
+
wh_dir_name = os.path.dirname(wh_name)
|
3028
|
+
wh_base_name = os.path.basename(wh_name)
|
3029
|
+
|
3030
|
+
cur = find_dir(wh_dir_name)
|
3031
|
+
rm = cur[wh_base_name]
|
3032
|
+
|
3033
|
+
if isinstance(rm, dict):
|
3034
|
+
# Whiteouts wipe out whole directory:
|
3035
|
+
# https://github.com/containerd/containerd/blob/59c8cf6ea5f4175ad512914dd5ce554942bf144f/pkg/archive/tar_test.go#L648
|
3036
|
+
# check.equal(set(rm), '')
|
3037
|
+
del cur[wh_base_name]
|
3038
|
+
|
3039
|
+
elif isinstance(rm, self._Entry):
|
3040
|
+
del cur[wh_base_name]
|
3041
|
+
|
3042
|
+
else:
|
3043
|
+
raise TypeError(rm)
|
3044
|
+
|
3045
|
+
if wh_opaques:
|
3046
|
+
raise NotImplementedError
|
3047
|
+
|
3048
|
+
#
|
3049
|
+
|
3050
|
+
out: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
3051
|
+
|
3052
|
+
def rec(cur): # noqa
|
3053
|
+
for _, child in sorted(cur.items(), key=lambda t: t[0]):
|
3054
|
+
if isinstance(child, dict):
|
3055
|
+
rec(child)
|
3056
|
+
|
3057
|
+
elif isinstance(child, self._Entry):
|
3058
|
+
check.not_in(child.info.name, out)
|
3059
|
+
out[child.info.name] = child
|
3060
|
+
|
3061
|
+
else:
|
3062
|
+
raise TypeError(child)
|
3063
|
+
|
3064
|
+
rec(root)
|
3065
|
+
|
3066
|
+
return out
|
3067
|
+
|
3068
|
+
#
|
3069
|
+
|
3070
|
+
@cached_nullary
|
3071
|
+
def _output_tar_file(self) -> tarfile.TarFile:
|
3072
|
+
return self._enter_context(tarfile.open(self._output_file_path, 'w'))
|
3073
|
+
|
3074
|
+
#
|
3075
|
+
|
3076
|
+
def _add_unpacked_entry(
|
3077
|
+
self,
|
3078
|
+
input_tar_file: tarfile.TarFile,
|
3079
|
+
info: tarfile.TarInfo,
|
3080
|
+
) -> None:
|
3081
|
+
base_name = os.path.basename(info.name)
|
3082
|
+
check.state(not base_name.startswith('.wh.'))
|
3083
|
+
|
3084
|
+
if info.type in tarfile.REGULAR_TYPES:
|
3085
|
+
with check.not_none(input_tar_file.extractfile(info)) as f:
|
3086
|
+
self._output_tar_file().addfile(info, f)
|
3087
|
+
|
3088
|
+
else:
|
3089
|
+
self._output_tar_file().addfile(info)
|
3090
|
+
|
3091
|
+
def _unpack_file(
|
3092
|
+
self,
|
3093
|
+
input_file: ta.Union[str, tarfile.TarFile],
|
3094
|
+
) -> None:
|
3095
|
+
entries_by_name = self._entries_by_name()
|
3096
|
+
|
3097
|
+
with self._open_input_file(input_file) as input_tar_file:
|
3098
|
+
info: tarfile.TarInfo
|
3099
|
+
for info in input_tar_file.getmembers():
|
3100
|
+
try:
|
3101
|
+
entry = entries_by_name[info.name]
|
3102
|
+
except KeyError:
|
3103
|
+
continue
|
3104
|
+
|
3105
|
+
if entry.file != input_file:
|
3106
|
+
continue
|
3107
|
+
|
3108
|
+
self._add_unpacked_entry(input_tar_file, info)
|
3109
|
+
|
3110
|
+
@cached_nullary
|
3111
|
+
def write(self) -> None:
|
3112
|
+
for input_file in self._input_files:
|
3113
|
+
self._unpack_file(input_file)
|
3114
|
+
|
3115
|
+
|
3116
|
+
########################################
|
3117
|
+
# ../../../omlish/argparse/cli.py
|
3118
|
+
"""
|
3119
|
+
FIXME:
|
3120
|
+
- exit_on_error lol
|
3121
|
+
|
3122
|
+
TODO:
|
3123
|
+
- default command
|
3124
|
+
- auto match all underscores to hyphens
|
3125
|
+
- pre-run, post-run hooks
|
3126
|
+
- exitstack?
|
3127
|
+
- suggestion - difflib.get_close_matches
|
3128
|
+
"""
|
3129
|
+
|
3130
|
+
|
3131
|
+
##
|
3132
|
+
|
3133
|
+
|
3134
|
+
@dc.dataclass(eq=False)
|
3135
|
+
class ArgparseArg:
|
3136
|
+
args: ta.Sequence[ta.Any]
|
3137
|
+
kwargs: ta.Mapping[str, ta.Any]
|
3138
|
+
dest: ta.Optional[str] = None
|
3139
|
+
|
3140
|
+
def __get__(self, instance, owner=None):
|
3141
|
+
if instance is None:
|
3142
|
+
return self
|
3143
|
+
return getattr(instance.args, self.dest) # type: ignore
|
3144
|
+
|
3145
|
+
|
3146
|
+
def argparse_arg(*args, **kwargs) -> ArgparseArg:
|
3147
|
+
return ArgparseArg(args, kwargs)
|
3148
|
+
|
3149
|
+
|
3150
|
+
def argparse_arg_(*args, **kwargs) -> ta.Any:
|
3151
|
+
return argparse_arg(*args, **kwargs)
|
3152
|
+
|
3153
|
+
|
3154
|
+
#
|
3155
|
+
|
3156
|
+
|
3157
|
+
@dc.dataclass(eq=False)
|
3158
|
+
class ArgparseCmd:
|
3159
|
+
name: str
|
3160
|
+
fn: ArgparseCmdFn
|
3161
|
+
args: ta.Sequence[ArgparseArg] = () # noqa
|
3162
|
+
|
3163
|
+
# _: dc.KW_ONLY
|
2771
3164
|
|
2772
3165
|
aliases: ta.Optional[ta.Sequence[str]] = None
|
2773
3166
|
parent: ta.Optional['ArgparseCmd'] = None
|
@@ -3447,239 +3840,49 @@ class HttpRequestParser:
|
|
3447
3840
|
return ParseHttpRequestError(
|
3448
3841
|
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3449
3842
|
message=('Line too long', str(err)),
|
3450
|
-
**result_kwargs(),
|
3451
|
-
)
|
3452
|
-
|
3453
|
-
except http.client.HTTPException as err:
|
3454
|
-
return ParseHttpRequestError(
|
3455
|
-
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3456
|
-
message=('Too many headers', str(err)),
|
3457
|
-
**result_kwargs(),
|
3458
|
-
)
|
3459
|
-
|
3460
|
-
# Check for connection directive
|
3461
|
-
|
3462
|
-
conn_type = headers.get('Connection', '')
|
3463
|
-
if conn_type.lower() == 'close':
|
3464
|
-
close_connection = True
|
3465
|
-
elif (
|
3466
|
-
conn_type.lower() == 'keep-alive' and
|
3467
|
-
version >= HttpProtocolVersions.HTTP_1_1
|
3468
|
-
):
|
3469
|
-
close_connection = False
|
3470
|
-
|
3471
|
-
# Check for expect directive
|
3472
|
-
|
3473
|
-
expect = headers.get('Expect', '')
|
3474
|
-
if (
|
3475
|
-
expect.lower() == '100-continue' and
|
3476
|
-
version >= HttpProtocolVersions.HTTP_1_1
|
3477
|
-
):
|
3478
|
-
expects_continue = True
|
3479
|
-
else:
|
3480
|
-
expects_continue = False
|
3481
|
-
|
3482
|
-
# Return
|
3483
|
-
|
3484
|
-
return ParsedHttpRequest(
|
3485
|
-
method=method,
|
3486
|
-
path=path,
|
3487
|
-
expects_continue=expects_continue,
|
3488
|
-
**result_kwargs(),
|
3489
|
-
)
|
3490
|
-
|
3491
|
-
def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
|
3492
|
-
return self._run_read_line_coro(self.coro_parse(), read_line)
|
3493
|
-
|
3494
|
-
|
3495
|
-
########################################
|
3496
|
-
# ../../../omlish/lite/contextmanagers.py
|
3497
|
-
|
3498
|
-
|
3499
|
-
##
|
3500
|
-
|
3501
|
-
|
3502
|
-
class ExitStacked:
|
3503
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3504
|
-
super().__init_subclass__(**kwargs)
|
3505
|
-
|
3506
|
-
for a in ('__enter__', '__exit__'):
|
3507
|
-
for b in cls.__bases__:
|
3508
|
-
if b is ExitStacked:
|
3509
|
-
continue
|
3510
|
-
try:
|
3511
|
-
fn = getattr(b, a)
|
3512
|
-
except AttributeError:
|
3513
|
-
pass
|
3514
|
-
else:
|
3515
|
-
if fn is not getattr(ExitStacked, a):
|
3516
|
-
raise TypeError(f'ExitStacked subclass {cls} must not not override {a} via {b}')
|
3517
|
-
|
3518
|
-
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
3519
|
-
|
3520
|
-
@contextlib.contextmanager
|
3521
|
-
def _exit_stacked_init_wrapper(self) -> ta.Iterator[None]:
|
3522
|
-
"""
|
3523
|
-
Overridable wrapper around __enter__ which deliberately does not have access to an _exit_stack yet. Intended for
|
3524
|
-
things like wrapping __enter__ in a lock.
|
3525
|
-
"""
|
3526
|
-
|
3527
|
-
yield
|
3528
|
-
|
3529
|
-
@ta.final
|
3530
|
-
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
3531
|
-
"""
|
3532
|
-
Final because any contexts entered during this init must be exited if any exception is thrown, and user
|
3533
|
-
overriding would likely interfere with that. Override `_enter_contexts` for such init.
|
3534
|
-
"""
|
3535
|
-
|
3536
|
-
with self._exit_stacked_init_wrapper():
|
3537
|
-
check.state(self._exit_stack is None)
|
3538
|
-
es = self._exit_stack = contextlib.ExitStack()
|
3539
|
-
es.__enter__()
|
3540
|
-
try:
|
3541
|
-
self._enter_contexts()
|
3542
|
-
except Exception: # noqa
|
3543
|
-
es.__exit__(*sys.exc_info())
|
3544
|
-
raise
|
3545
|
-
return self
|
3546
|
-
|
3547
|
-
@ta.final
|
3548
|
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
3549
|
-
if (es := self._exit_stack) is None:
|
3550
|
-
return None
|
3551
|
-
try:
|
3552
|
-
self._exit_contexts()
|
3553
|
-
except Exception: # noqa
|
3554
|
-
es.__exit__(*sys.exc_info())
|
3555
|
-
raise
|
3556
|
-
return es.__exit__(exc_type, exc_val, exc_tb)
|
3557
|
-
|
3558
|
-
def _enter_contexts(self) -> None:
|
3559
|
-
pass
|
3560
|
-
|
3561
|
-
def _exit_contexts(self) -> None:
|
3562
|
-
pass
|
3563
|
-
|
3564
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3565
|
-
es = check.not_none(self._exit_stack)
|
3566
|
-
return es.enter_context(cm)
|
3567
|
-
|
3568
|
-
|
3569
|
-
class AsyncExitStacked:
|
3570
|
-
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3571
|
-
super().__init_subclass__(**kwargs)
|
3572
|
-
|
3573
|
-
for a in ('__aenter__', '__aexit__'):
|
3574
|
-
for b in cls.__bases__:
|
3575
|
-
if b is AsyncExitStacked:
|
3576
|
-
continue
|
3577
|
-
try:
|
3578
|
-
fn = getattr(b, a)
|
3579
|
-
except AttributeError:
|
3580
|
-
pass
|
3581
|
-
else:
|
3582
|
-
if fn is not getattr(AsyncExitStacked, a):
|
3583
|
-
raise TypeError(f'AsyncExitStacked subclass {cls} must not not override {a} via {b}')
|
3584
|
-
|
3585
|
-
_exit_stack: ta.Optional[contextlib.AsyncExitStack] = None
|
3586
|
-
|
3587
|
-
@contextlib.asynccontextmanager
|
3588
|
-
async def _async_exit_stacked_init_wrapper(self) -> ta.AsyncGenerator[None, None]:
|
3589
|
-
yield
|
3590
|
-
|
3591
|
-
@ta.final
|
3592
|
-
async def __aenter__(self: AsyncExitStackedT) -> AsyncExitStackedT:
|
3593
|
-
async with self._async_exit_stacked_init_wrapper():
|
3594
|
-
check.state(self._exit_stack is None)
|
3595
|
-
es = self._exit_stack = contextlib.AsyncExitStack()
|
3596
|
-
await es.__aenter__()
|
3597
|
-
try:
|
3598
|
-
await self._async_enter_contexts()
|
3599
|
-
except Exception: # noqa
|
3600
|
-
await es.__aexit__(*sys.exc_info())
|
3601
|
-
raise
|
3602
|
-
return self
|
3603
|
-
|
3604
|
-
@ta.final
|
3605
|
-
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
3606
|
-
if (es := self._exit_stack) is None:
|
3607
|
-
return None
|
3608
|
-
try:
|
3609
|
-
await self._async_exit_contexts()
|
3610
|
-
except Exception: # noqa
|
3611
|
-
await es.__aexit__(*sys.exc_info())
|
3612
|
-
raise
|
3613
|
-
return await es.__aexit__(exc_type, exc_val, exc_tb)
|
3614
|
-
|
3615
|
-
async def _async_enter_contexts(self) -> None:
|
3616
|
-
pass
|
3617
|
-
|
3618
|
-
async def _async_exit_contexts(self) -> None:
|
3619
|
-
pass
|
3620
|
-
|
3621
|
-
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3622
|
-
es = check.not_none(self._exit_stack)
|
3623
|
-
return es.enter_context(cm)
|
3624
|
-
|
3625
|
-
async def _enter_async_context(self, cm: ta.AsyncContextManager[T]) -> T:
|
3626
|
-
es = check.not_none(self._exit_stack)
|
3627
|
-
return await es.enter_async_context(cm)
|
3628
|
-
|
3629
|
-
|
3630
|
-
##
|
3631
|
-
|
3632
|
-
|
3633
|
-
@contextlib.contextmanager
|
3634
|
-
def defer(fn: ta.Callable, *args: ta.Any, **kwargs: ta.Any) -> ta.Generator[ta.Callable, None, None]:
|
3635
|
-
if args or kwargs:
|
3636
|
-
fn = functools.partial(fn, *args, **kwargs)
|
3637
|
-
try:
|
3638
|
-
yield fn
|
3639
|
-
finally:
|
3640
|
-
fn()
|
3641
|
-
|
3642
|
-
|
3643
|
-
@contextlib.asynccontextmanager
|
3644
|
-
async def adefer(fn: ta.Awaitable) -> ta.AsyncGenerator[ta.Awaitable, None]:
|
3645
|
-
try:
|
3646
|
-
yield fn
|
3647
|
-
finally:
|
3648
|
-
await fn
|
3649
|
-
|
3650
|
-
|
3651
|
-
##
|
3843
|
+
**result_kwargs(),
|
3844
|
+
)
|
3652
3845
|
|
3846
|
+
except http.client.HTTPException as err:
|
3847
|
+
return ParseHttpRequestError(
|
3848
|
+
code=http.HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE,
|
3849
|
+
message=('Too many headers', str(err)),
|
3850
|
+
**result_kwargs(),
|
3851
|
+
)
|
3653
3852
|
|
3654
|
-
|
3655
|
-
def attr_setting(obj, attr, val, *, default=None): # noqa
|
3656
|
-
not_set = object()
|
3657
|
-
orig = getattr(obj, attr, not_set)
|
3658
|
-
try:
|
3659
|
-
setattr(obj, attr, val)
|
3660
|
-
if orig is not not_set:
|
3661
|
-
yield orig
|
3662
|
-
else:
|
3663
|
-
yield default
|
3664
|
-
finally:
|
3665
|
-
if orig is not_set:
|
3666
|
-
delattr(obj, attr)
|
3667
|
-
else:
|
3668
|
-
setattr(obj, attr, orig)
|
3853
|
+
# Check for connection directive
|
3669
3854
|
|
3855
|
+
conn_type = headers.get('Connection', '')
|
3856
|
+
if conn_type.lower() == 'close':
|
3857
|
+
close_connection = True
|
3858
|
+
elif (
|
3859
|
+
conn_type.lower() == 'keep-alive' and
|
3860
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
3861
|
+
):
|
3862
|
+
close_connection = False
|
3670
3863
|
|
3671
|
-
|
3864
|
+
# Check for expect directive
|
3672
3865
|
|
3866
|
+
expect = headers.get('Expect', '')
|
3867
|
+
if (
|
3868
|
+
expect.lower() == '100-continue' and
|
3869
|
+
version >= HttpProtocolVersions.HTTP_1_1
|
3870
|
+
):
|
3871
|
+
expects_continue = True
|
3872
|
+
else:
|
3873
|
+
expects_continue = False
|
3673
3874
|
|
3674
|
-
|
3675
|
-
def __init__(self, thing):
|
3676
|
-
self.thing = thing
|
3875
|
+
# Return
|
3677
3876
|
|
3678
|
-
|
3679
|
-
|
3877
|
+
return ParsedHttpRequest(
|
3878
|
+
method=method,
|
3879
|
+
path=path,
|
3880
|
+
expects_continue=expects_continue,
|
3881
|
+
**result_kwargs(),
|
3882
|
+
)
|
3680
3883
|
|
3681
|
-
|
3682
|
-
|
3884
|
+
def parse(self, read_line: ta.Callable[[int], bytes]) -> ParseHttpRequestResult:
|
3885
|
+
return self._run_read_line_coro(self.coro_parse(), read_line)
|
3683
3886
|
|
3684
3887
|
|
3685
3888
|
########################################
|
@@ -7374,204 +7577,6 @@ def get_single_oci_image_manifest(image_index: OciImageIndex) -> OciImageManifes
|
|
7374
7577
|
return check.isinstance(child_index, OciImageManifest)
|
7375
7578
|
|
7376
7579
|
|
7377
|
-
########################################
|
7378
|
-
# ../../oci/pack/unpacking.py
|
7379
|
-
|
7380
|
-
|
7381
|
-
##
|
7382
|
-
|
7383
|
-
|
7384
|
-
class OciLayerUnpacker(ExitStacked):
|
7385
|
-
def __init__(
|
7386
|
-
self,
|
7387
|
-
input_files: ta.Sequence[ta.Union[str, tarfile.TarFile]],
|
7388
|
-
output_file_path: str,
|
7389
|
-
) -> None:
|
7390
|
-
super().__init__()
|
7391
|
-
|
7392
|
-
self._input_files = list(input_files)
|
7393
|
-
self._output_file_path = output_file_path
|
7394
|
-
|
7395
|
-
#
|
7396
|
-
|
7397
|
-
@contextlib.contextmanager
|
7398
|
-
def _open_input_file(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Iterator[tarfile.TarFile]:
|
7399
|
-
if isinstance(input_file, tarfile.TarFile):
|
7400
|
-
yield input_file
|
7401
|
-
|
7402
|
-
elif isinstance(input_file, str):
|
7403
|
-
with tarfile.open(input_file) as tar_file:
|
7404
|
-
yield tar_file
|
7405
|
-
|
7406
|
-
else:
|
7407
|
-
raise TypeError(input_file)
|
7408
|
-
|
7409
|
-
#
|
7410
|
-
|
7411
|
-
class _Entry(ta.NamedTuple):
|
7412
|
-
file: ta.Union[str, tarfile.TarFile]
|
7413
|
-
info: tarfile.TarInfo
|
7414
|
-
|
7415
|
-
def _build_input_file_sorted_entries(self, input_file: ta.Union[str, tarfile.TarFile]) -> ta.Sequence[_Entry]:
|
7416
|
-
dct: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
7417
|
-
|
7418
|
-
with self._open_input_file(input_file) as input_tar_file:
|
7419
|
-
for info in input_tar_file.getmembers():
|
7420
|
-
check.not_in(info.name, dct)
|
7421
|
-
dct[info.name] = self._Entry(
|
7422
|
-
file=input_file,
|
7423
|
-
info=info,
|
7424
|
-
)
|
7425
|
-
|
7426
|
-
return sorted(dct.values(), key=lambda entry: entry.info.name)
|
7427
|
-
|
7428
|
-
@cached_nullary
|
7429
|
-
def _entries_by_name(self) -> ta.Mapping[str, _Entry]:
|
7430
|
-
root: dict = {}
|
7431
|
-
|
7432
|
-
def find_dir(dir_name: str) -> dict: # noqa
|
7433
|
-
if dir_name:
|
7434
|
-
dir_parts = dir_name.split('/')
|
7435
|
-
else:
|
7436
|
-
dir_parts = []
|
7437
|
-
|
7438
|
-
cur = root # noqa
|
7439
|
-
for dir_part in dir_parts:
|
7440
|
-
cur = cur[dir_part] # noqa
|
7441
|
-
|
7442
|
-
return check.isinstance(cur, dict)
|
7443
|
-
|
7444
|
-
#
|
7445
|
-
|
7446
|
-
for input_file in self._input_files:
|
7447
|
-
sorted_entries = self._build_input_file_sorted_entries(input_file)
|
7448
|
-
|
7449
|
-
wh_names = set()
|
7450
|
-
wh_opaques = set()
|
7451
|
-
|
7452
|
-
#
|
7453
|
-
|
7454
|
-
for entry in sorted_entries:
|
7455
|
-
info = entry.info
|
7456
|
-
name = check.non_empty_str(info.name)
|
7457
|
-
base_name = os.path.basename(name)
|
7458
|
-
dir_name = os.path.dirname(name)
|
7459
|
-
|
7460
|
-
if base_name == '.wh..wh..opq':
|
7461
|
-
wh_opaques.add(dir_name)
|
7462
|
-
continue
|
7463
|
-
|
7464
|
-
if base_name.startswith('.wh.'):
|
7465
|
-
wh_base_name = os.path.basename(base_name[4:])
|
7466
|
-
wh_name = os.path.join(dir_name, wh_base_name)
|
7467
|
-
wh_names.add(wh_name)
|
7468
|
-
continue
|
7469
|
-
|
7470
|
-
cur = find_dir(dir_name)
|
7471
|
-
|
7472
|
-
if info.type == tarfile.DIRTYPE:
|
7473
|
-
try:
|
7474
|
-
ex = cur[base_name]
|
7475
|
-
except KeyError:
|
7476
|
-
cur[base_name] = {'': entry}
|
7477
|
-
else:
|
7478
|
-
ex[''] = entry
|
7479
|
-
|
7480
|
-
else:
|
7481
|
-
cur[base_name] = entry
|
7482
|
-
|
7483
|
-
#
|
7484
|
-
|
7485
|
-
for wh_name in reversed(sorted(wh_names)): # noqa
|
7486
|
-
wh_dir_name = os.path.dirname(wh_name)
|
7487
|
-
wh_base_name = os.path.basename(wh_name)
|
7488
|
-
|
7489
|
-
cur = find_dir(wh_dir_name)
|
7490
|
-
rm = cur[wh_base_name]
|
7491
|
-
|
7492
|
-
if isinstance(rm, dict):
|
7493
|
-
# Whiteouts wipe out whole directory:
|
7494
|
-
# https://github.com/containerd/containerd/blob/59c8cf6ea5f4175ad512914dd5ce554942bf144f/pkg/archive/tar_test.go#L648
|
7495
|
-
# check.equal(set(rm), '')
|
7496
|
-
del cur[wh_base_name]
|
7497
|
-
|
7498
|
-
elif isinstance(rm, self._Entry):
|
7499
|
-
del cur[wh_base_name]
|
7500
|
-
|
7501
|
-
else:
|
7502
|
-
raise TypeError(rm)
|
7503
|
-
|
7504
|
-
if wh_opaques:
|
7505
|
-
raise NotImplementedError
|
7506
|
-
|
7507
|
-
#
|
7508
|
-
|
7509
|
-
out: ta.Dict[str, OciLayerUnpacker._Entry] = {}
|
7510
|
-
|
7511
|
-
def rec(cur): # noqa
|
7512
|
-
for _, child in sorted(cur.items(), key=lambda t: t[0]):
|
7513
|
-
if isinstance(child, dict):
|
7514
|
-
rec(child)
|
7515
|
-
|
7516
|
-
elif isinstance(child, self._Entry):
|
7517
|
-
check.not_in(child.info.name, out)
|
7518
|
-
out[child.info.name] = child
|
7519
|
-
|
7520
|
-
else:
|
7521
|
-
raise TypeError(child)
|
7522
|
-
|
7523
|
-
rec(root)
|
7524
|
-
|
7525
|
-
return out
|
7526
|
-
|
7527
|
-
#
|
7528
|
-
|
7529
|
-
@cached_nullary
|
7530
|
-
def _output_tar_file(self) -> tarfile.TarFile:
|
7531
|
-
return self._enter_context(tarfile.open(self._output_file_path, 'w'))
|
7532
|
-
|
7533
|
-
#
|
7534
|
-
|
7535
|
-
def _add_unpacked_entry(
|
7536
|
-
self,
|
7537
|
-
input_tar_file: tarfile.TarFile,
|
7538
|
-
info: tarfile.TarInfo,
|
7539
|
-
) -> None:
|
7540
|
-
base_name = os.path.basename(info.name)
|
7541
|
-
check.state(not base_name.startswith('.wh.'))
|
7542
|
-
|
7543
|
-
if info.type in tarfile.REGULAR_TYPES:
|
7544
|
-
with check.not_none(input_tar_file.extractfile(info)) as f:
|
7545
|
-
self._output_tar_file().addfile(info, f)
|
7546
|
-
|
7547
|
-
else:
|
7548
|
-
self._output_tar_file().addfile(info)
|
7549
|
-
|
7550
|
-
def _unpack_file(
|
7551
|
-
self,
|
7552
|
-
input_file: ta.Union[str, tarfile.TarFile],
|
7553
|
-
) -> None:
|
7554
|
-
entries_by_name = self._entries_by_name()
|
7555
|
-
|
7556
|
-
with self._open_input_file(input_file) as input_tar_file:
|
7557
|
-
info: tarfile.TarInfo
|
7558
|
-
for info in input_tar_file.getmembers():
|
7559
|
-
try:
|
7560
|
-
entry = entries_by_name[info.name]
|
7561
|
-
except KeyError:
|
7562
|
-
continue
|
7563
|
-
|
7564
|
-
if entry.file != input_file:
|
7565
|
-
continue
|
7566
|
-
|
7567
|
-
self._add_unpacked_entry(input_tar_file, info)
|
7568
|
-
|
7569
|
-
@cached_nullary
|
7570
|
-
def write(self) -> None:
|
7571
|
-
for input_file in self._input_files:
|
7572
|
-
self._unpack_file(input_file)
|
7573
|
-
|
7574
|
-
|
7575
7580
|
########################################
|
7576
7581
|
# ../../oci/repositories.py
|
7577
7582
|
|