ominfra 0.0.0.dev199__py3-none-any.whl → 0.0.0.dev201__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.
- ominfra/clouds/aws/models/__init__.py +0 -0
- ominfra/clouds/aws/models/base.py +101 -0
- ominfra/clouds/aws/models/gen/__init__.py +0 -0
- ominfra/clouds/aws/models/gen/__main__.py +4 -0
- ominfra/clouds/aws/models/gen/cli.py +139 -0
- ominfra/clouds/aws/models/gen/gen.py +497 -0
- ominfra/clouds/aws/models/services/__init__.py +0 -0
- ominfra/clouds/aws/models/services/ec2.py +5093 -0
- ominfra/clouds/aws/models/services/lambda_.py +864 -0
- ominfra/clouds/aws/models/services/rds.py +1898 -0
- ominfra/clouds/aws/models/services/s3.py +1387 -0
- ominfra/clouds/aws/models/services/services.toml +41 -0
- ominfra/commands/__init__.py +0 -0
- ominfra/{ssh.py → commands/ssh.py} +2 -2
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/METADATA +5 -5
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/RECORD +21 -8
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/WHEEL +1 -1
- /ominfra/{cmds.py → commands/runners.py} +0 -0
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev199.dist-info → ominfra-0.0.0.dev201.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,497 @@
|
|
1
|
+
"""
|
2
|
+
TODO:
|
3
|
+
- default values? nullability? maybe a new_default helper?
|
4
|
+
- relative import base
|
5
|
+
"""
|
6
|
+
import builtins
|
7
|
+
import dataclasses as dc
|
8
|
+
import io
|
9
|
+
import typing as ta
|
10
|
+
|
11
|
+
from omlish import check
|
12
|
+
from omlish import collections as col
|
13
|
+
from omlish import lang
|
14
|
+
|
15
|
+
|
16
|
+
if ta.TYPE_CHECKING:
|
17
|
+
import botocore.loaders
|
18
|
+
import botocore.model
|
19
|
+
import botocore.session
|
20
|
+
else:
|
21
|
+
botocore = lang.proxy_import('botocore', extras=[
|
22
|
+
'loaders',
|
23
|
+
'model',
|
24
|
+
'session',
|
25
|
+
])
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
ServiceTypeName: ta.TypeAlias = ta.Literal[
|
32
|
+
'service-2',
|
33
|
+
'paginators-1',
|
34
|
+
'waiters-2',
|
35
|
+
]
|
36
|
+
|
37
|
+
|
38
|
+
class ModelGen:
|
39
|
+
def __init__(
|
40
|
+
self,
|
41
|
+
service_model: 'botocore.model.ServiceModel',
|
42
|
+
*,
|
43
|
+
shape_names: ta.Iterable[str] = (),
|
44
|
+
operation_names: ta.Iterable[str] = (),
|
45
|
+
) -> None:
|
46
|
+
super().__init__()
|
47
|
+
|
48
|
+
self._service_model = service_model
|
49
|
+
self._shape_names = list(check.not_isinstance(shape_names, str))
|
50
|
+
self._operation_names = list(check.not_isinstance(operation_names, str))
|
51
|
+
|
52
|
+
@property
|
53
|
+
def service_model(self) -> 'botocore.model.ServiceModel':
|
54
|
+
return self._service_model
|
55
|
+
|
56
|
+
@property
|
57
|
+
def shape_names(self) -> ta.Sequence[str]:
|
58
|
+
return self._shape_names
|
59
|
+
|
60
|
+
@property
|
61
|
+
def operation_names(self) -> ta.Sequence[str]:
|
62
|
+
return self._operation_names
|
63
|
+
|
64
|
+
#
|
65
|
+
|
66
|
+
@classmethod
|
67
|
+
def create_data_loader(cls) -> 'botocore.loaders.Loader':
|
68
|
+
session = botocore.session.get_session()
|
69
|
+
return session.get_component('data_loader')
|
70
|
+
|
71
|
+
@classmethod
|
72
|
+
def list_available_services(
|
73
|
+
cls,
|
74
|
+
*,
|
75
|
+
type_name: ServiceTypeName = 'service-2',
|
76
|
+
) -> list[str]:
|
77
|
+
loader = cls.create_data_loader()
|
78
|
+
return list(loader.list_available_services(type_name))
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def load_service_model(
|
82
|
+
cls,
|
83
|
+
service_name: str,
|
84
|
+
*,
|
85
|
+
type_name: ServiceTypeName = 'service-2',
|
86
|
+
api_version: str | None = None,
|
87
|
+
) -> 'botocore.model.ServiceModel':
|
88
|
+
loader = cls.create_data_loader()
|
89
|
+
json_model = loader.load_service_model(service_name, type_name, api_version=api_version)
|
90
|
+
return botocore.model.ServiceModel(json_model, service_name=service_name)
|
91
|
+
|
92
|
+
@classmethod
|
93
|
+
def get_referenced_shape_names(
|
94
|
+
cls,
|
95
|
+
service_model: 'botocore.model.ServiceModel',
|
96
|
+
*,
|
97
|
+
shape_names: ta.Iterable[str] = (),
|
98
|
+
operation_names: ta.Iterable[str] = (),
|
99
|
+
) -> list[str]:
|
100
|
+
todo = set(check.not_isinstance(shape_names, str))
|
101
|
+
|
102
|
+
for on in operation_names:
|
103
|
+
op = service_model.operation_model(on)
|
104
|
+
for osh in [
|
105
|
+
op.input_shape,
|
106
|
+
op.output_shape,
|
107
|
+
*op.error_shapes,
|
108
|
+
]:
|
109
|
+
if osh is not None:
|
110
|
+
todo.add(osh.name)
|
111
|
+
|
112
|
+
seen = set(cls.BASE_TYPE_ANNS)
|
113
|
+
|
114
|
+
dct: dict[str, set[str]] = {}
|
115
|
+
while todo:
|
116
|
+
cur = todo.pop()
|
117
|
+
seen.add(cur)
|
118
|
+
|
119
|
+
shape: botocore.model.Shape = service_model.shape_for(cur)
|
120
|
+
|
121
|
+
if isinstance(shape, botocore.model.StructureShape):
|
122
|
+
deps = {m.name for m in shape.members.values()}
|
123
|
+
|
124
|
+
elif isinstance(shape, botocore.model.MapShape):
|
125
|
+
deps = {shape.key.name, shape.value.name}
|
126
|
+
|
127
|
+
elif isinstance(shape, botocore.model.ListShape):
|
128
|
+
deps = {shape.member.name}
|
129
|
+
|
130
|
+
else:
|
131
|
+
deps = set()
|
132
|
+
|
133
|
+
dct[shape.name] = deps
|
134
|
+
todo.update(deps - seen)
|
135
|
+
|
136
|
+
return list(lang.flatten(sorted(s - cls.BASE_SHAPE_NAMES) for s in col.mut_toposort(dct)))
|
137
|
+
|
138
|
+
#
|
139
|
+
|
140
|
+
BASE_TYPE_ANNS: ta.ClassVar[ta.Mapping[str, str]] = {
|
141
|
+
'Boolean': 'bool',
|
142
|
+
'Integer': 'int',
|
143
|
+
'String': 'str',
|
144
|
+
'DateTime': '_base.DateTime',
|
145
|
+
'MillisecondDateTime': '_base.MillisecondDateTime',
|
146
|
+
'TagList': '_base.TagList',
|
147
|
+
}
|
148
|
+
|
149
|
+
BASE_SHAPE_NAMES: ta.ClassVar[ta.AbstractSet[str]] = set(BASE_TYPE_ANNS)
|
150
|
+
|
151
|
+
def get_type_ann(
|
152
|
+
self,
|
153
|
+
name: str,
|
154
|
+
*,
|
155
|
+
unquoted_names: bool = False,
|
156
|
+
) -> str | None:
|
157
|
+
try:
|
158
|
+
return self.BASE_TYPE_ANNS[name]
|
159
|
+
except KeyError:
|
160
|
+
pass
|
161
|
+
|
162
|
+
if name in self._shape_names:
|
163
|
+
name = self.sanitize_class_name(name)
|
164
|
+
if not unquoted_names:
|
165
|
+
return f"'{name}'"
|
166
|
+
else:
|
167
|
+
return name
|
168
|
+
|
169
|
+
return None
|
170
|
+
|
171
|
+
#
|
172
|
+
|
173
|
+
DEMANGLE_PREFIXES: ta.ClassVar[ta.Sequence[str]] = [
|
174
|
+
'AAAA',
|
175
|
+
'ACL',
|
176
|
+
'ACP',
|
177
|
+
'AES',
|
178
|
+
'AES256',
|
179
|
+
'AZ',
|
180
|
+
'CA',
|
181
|
+
'CRC32',
|
182
|
+
'CRC32C',
|
183
|
+
'DB',
|
184
|
+
'EFS',
|
185
|
+
'ETag',
|
186
|
+
'IAM',
|
187
|
+
'IO',
|
188
|
+
'IP',
|
189
|
+
'JSON',
|
190
|
+
'KMS',
|
191
|
+
'MD5',
|
192
|
+
'MFA',
|
193
|
+
'SHA1',
|
194
|
+
'SHA256',
|
195
|
+
'SSE',
|
196
|
+
'TTL',
|
197
|
+
]
|
198
|
+
|
199
|
+
def demangle_name(self, n: str) -> str:
|
200
|
+
ps: list[str] = []
|
201
|
+
while n:
|
202
|
+
ms: list[tuple[str, int]] = []
|
203
|
+
|
204
|
+
for pfx in self.DEMANGLE_PREFIXES:
|
205
|
+
if (i := n.find(pfx)) >= 0:
|
206
|
+
ms.append((pfx, i))
|
207
|
+
|
208
|
+
if not ms:
|
209
|
+
ps.append(n)
|
210
|
+
break
|
211
|
+
|
212
|
+
if len(ms) > 1:
|
213
|
+
m = sorted(ms, key=lambda t: (t[1], -len(t[0])))[0]
|
214
|
+
else:
|
215
|
+
m = ms[0]
|
216
|
+
|
217
|
+
pfx, i = m
|
218
|
+
l, r = n[:i], n[i + len(pfx):]
|
219
|
+
|
220
|
+
if l:
|
221
|
+
ps.append(l)
|
222
|
+
ps.append(pfx.lower())
|
223
|
+
|
224
|
+
n = r
|
225
|
+
|
226
|
+
return '_'.join(lang.snake_case(p) for p in ps)
|
227
|
+
|
228
|
+
#
|
229
|
+
|
230
|
+
def sanitize_class_name(self, n: str) -> str:
|
231
|
+
if hasattr(builtins, n):
|
232
|
+
n += '_'
|
233
|
+
return n
|
234
|
+
|
235
|
+
#
|
236
|
+
|
237
|
+
PREAMBLE_LINES: ta.Sequence[str] = [
|
238
|
+
'# flake8: noqa: E501',
|
239
|
+
'# ruff: noqa: N801 S105',
|
240
|
+
'# fmt: off',
|
241
|
+
'import dataclasses as _dc # noqa',
|
242
|
+
'import enum as _enum # noqa',
|
243
|
+
'import typing as _ta # noqa',
|
244
|
+
'',
|
245
|
+
'from .. import base as _base # noqa',
|
246
|
+
'',
|
247
|
+
'',
|
248
|
+
'##',
|
249
|
+
'',
|
250
|
+
'',
|
251
|
+
]
|
252
|
+
|
253
|
+
def gen_preamble(self) -> str:
|
254
|
+
return '\n'.join(self.PREAMBLE_LINES)
|
255
|
+
|
256
|
+
#
|
257
|
+
|
258
|
+
PRIMITIVE_SHAPE_TYPES: ta.ClassVar[ta.Mapping[str, str]] = {
|
259
|
+
'integer': 'int',
|
260
|
+
'long': 'int',
|
261
|
+
'blob': 'bytes',
|
262
|
+
'boolean': 'bool',
|
263
|
+
'timestamp': '_base.Timestamp',
|
264
|
+
}
|
265
|
+
|
266
|
+
@dc.dataclass(frozen=True)
|
267
|
+
class ShapeSrc:
|
268
|
+
src: str
|
269
|
+
|
270
|
+
class_name: str | None = dc.field(default=None, kw_only=True)
|
271
|
+
double_space: bool = False
|
272
|
+
|
273
|
+
def gen_shape(
|
274
|
+
self,
|
275
|
+
name: str,
|
276
|
+
*,
|
277
|
+
unquoted_names: bool = False,
|
278
|
+
) -> ShapeSrc:
|
279
|
+
shape: botocore.model.Shape = self._service_model.shape_for(name)
|
280
|
+
|
281
|
+
san_name = self.sanitize_class_name(shape.name)
|
282
|
+
|
283
|
+
if isinstance(shape, botocore.model.StructureShape):
|
284
|
+
lines: list[str] = []
|
285
|
+
|
286
|
+
mds = [
|
287
|
+
f'shape_name={shape.name!r}',
|
288
|
+
]
|
289
|
+
|
290
|
+
lines.extend([
|
291
|
+
'@_dc.dataclass(frozen=True)',
|
292
|
+
f'class {san_name}(',
|
293
|
+
' _base.Shape,',
|
294
|
+
*[f' {dl},' for dl in mds],
|
295
|
+
'):',
|
296
|
+
])
|
297
|
+
|
298
|
+
if not shape.members:
|
299
|
+
lines.append(' pass')
|
300
|
+
|
301
|
+
for i, (mn, ms) in enumerate(shape.members.items()):
|
302
|
+
if i:
|
303
|
+
lines.append('')
|
304
|
+
fn = self.demangle_name(mn)
|
305
|
+
mds = [
|
306
|
+
f'member_name={mn!r}',
|
307
|
+
f'shape_name={ms.name!r}',
|
308
|
+
]
|
309
|
+
ma = self.get_type_ann(
|
310
|
+
ms.name,
|
311
|
+
unquoted_names=unquoted_names,
|
312
|
+
)
|
313
|
+
fls = [
|
314
|
+
f'{fn}: {ma or ms.name} = _dc.field(metadata=_base.field_metadata(',
|
315
|
+
*[f' {dl},' for dl in mds],
|
316
|
+
'))',
|
317
|
+
]
|
318
|
+
if ma is None:
|
319
|
+
fls = ['# ' + fl for fl in fls]
|
320
|
+
lines.append('\n'.join(' ' + fl for fl in fls))
|
321
|
+
|
322
|
+
return self.ShapeSrc(
|
323
|
+
'\n'.join(lines),
|
324
|
+
class_name=san_name,
|
325
|
+
double_space=True,
|
326
|
+
)
|
327
|
+
|
328
|
+
elif isinstance(shape, botocore.model.ListShape):
|
329
|
+
mn = shape.member.name
|
330
|
+
ma = self.get_type_ann(
|
331
|
+
mn,
|
332
|
+
unquoted_names=unquoted_names,
|
333
|
+
)
|
334
|
+
l = f'{san_name}: _ta.TypeAlias = _ta.Sequence[{ma or mn}]'
|
335
|
+
if ma is None:
|
336
|
+
l = '# ' + l
|
337
|
+
return self.ShapeSrc(l)
|
338
|
+
|
339
|
+
elif isinstance(shape, botocore.model.MapShape):
|
340
|
+
# shape.key, shape.value
|
341
|
+
kn = shape.key.name
|
342
|
+
ka = self.get_type_ann(
|
343
|
+
kn,
|
344
|
+
unquoted_names=unquoted_names,
|
345
|
+
)
|
346
|
+
vn = shape.key.name
|
347
|
+
va = self.get_type_ann(
|
348
|
+
vn,
|
349
|
+
unquoted_names=unquoted_names,
|
350
|
+
)
|
351
|
+
l = f'{san_name}: _ta.TypeAlias = _ta.Mapping[{ka or kn}, {va or vn}]'
|
352
|
+
if ka is None or va is None:
|
353
|
+
l = '# ' + l
|
354
|
+
return self.ShapeSrc(l)
|
355
|
+
|
356
|
+
elif isinstance(shape, botocore.model.StringShape):
|
357
|
+
if shape.enum:
|
358
|
+
ls = [
|
359
|
+
f'class {san_name}(_enum.Enum):',
|
360
|
+
]
|
361
|
+
all_caps = all(v == v.upper() for v in shape.enum)
|
362
|
+
for v in shape.enum:
|
363
|
+
n = v
|
364
|
+
if not all_caps:
|
365
|
+
n = self.demangle_name(n)
|
366
|
+
n = n.upper()
|
367
|
+
for c in '.-:':
|
368
|
+
n = n.replace(c, '_')
|
369
|
+
ls.append(f' {n} = {v!r}')
|
370
|
+
return self.ShapeSrc(
|
371
|
+
'\n'.join(ls),
|
372
|
+
double_space=True,
|
373
|
+
)
|
374
|
+
|
375
|
+
else:
|
376
|
+
return self.ShapeSrc(f"{san_name} = _ta.NewType('{san_name}', str)")
|
377
|
+
|
378
|
+
elif (pt := self.PRIMITIVE_SHAPE_TYPES.get(shape.type_name)) is not None:
|
379
|
+
return self.ShapeSrc(f'{san_name} = _ta.NewType({san_name!r}, {pt})')
|
380
|
+
|
381
|
+
else:
|
382
|
+
raise TypeError(shape.type_name)
|
383
|
+
|
384
|
+
def gen_all_shapes(
|
385
|
+
self,
|
386
|
+
out: ta.TextIO,
|
387
|
+
*,
|
388
|
+
unquoted_names: bool = False,
|
389
|
+
) -> None:
|
390
|
+
shape_srcs = [
|
391
|
+
self.gen_shape(
|
392
|
+
shape_name,
|
393
|
+
unquoted_names=unquoted_names,
|
394
|
+
)
|
395
|
+
for shape_name in self.shape_names
|
396
|
+
]
|
397
|
+
|
398
|
+
all_shapes: list[str] = []
|
399
|
+
|
400
|
+
prev_shape_src: ModelGen.ShapeSrc | None = None
|
401
|
+
for shape_src in shape_srcs:
|
402
|
+
if prev_shape_src is not None:
|
403
|
+
out.write('\n')
|
404
|
+
if shape_src.double_space or prev_shape_src.double_space:
|
405
|
+
out.write('\n')
|
406
|
+
out.write(shape_src.src)
|
407
|
+
out.write('\n')
|
408
|
+
if shape_src.class_name is not None:
|
409
|
+
all_shapes.append(shape_src.class_name)
|
410
|
+
prev_shape_src = shape_src
|
411
|
+
|
412
|
+
out.write('\n\n')
|
413
|
+
out.write('ALL_SHAPES: frozenset[type[_base.Shape]] = frozenset([\n')
|
414
|
+
for n in sorted(all_shapes):
|
415
|
+
out.write(f' {n},\n')
|
416
|
+
out.write('])\n')
|
417
|
+
|
418
|
+
#
|
419
|
+
|
420
|
+
@dc.dataclass(frozen=True)
|
421
|
+
class OperationSrc:
|
422
|
+
src: str
|
423
|
+
name: str
|
424
|
+
|
425
|
+
def gen_operation(
|
426
|
+
self,
|
427
|
+
name: str,
|
428
|
+
) -> OperationSrc:
|
429
|
+
operation: botocore.model.OperationModel = self._service_model.operation_model(name)
|
430
|
+
|
431
|
+
dcn = self.demangle_name(operation.name).upper()
|
432
|
+
|
433
|
+
fls = [
|
434
|
+
f'name={operation.name!r},',
|
435
|
+
]
|
436
|
+
|
437
|
+
if operation.input_shape is not None:
|
438
|
+
fls.append(f'input={operation.input_shape.name},')
|
439
|
+
if operation.output_shape is not None:
|
440
|
+
fls.append(f'output={operation.output_shape.name},')
|
441
|
+
|
442
|
+
if operation.error_shapes:
|
443
|
+
fls.append('errors=[')
|
444
|
+
for osn in [es.name for es in operation.error_shapes]:
|
445
|
+
fls.append(f' {osn},')
|
446
|
+
fls.append('],')
|
447
|
+
|
448
|
+
lines = [
|
449
|
+
f'{dcn} = _base.Operation(',
|
450
|
+
*[f' {fl}' for fl in fls],
|
451
|
+
')',
|
452
|
+
]
|
453
|
+
|
454
|
+
return self.OperationSrc('\n'.join(lines), dcn)
|
455
|
+
|
456
|
+
def gen_all_operations(
|
457
|
+
self,
|
458
|
+
out: ta.TextIO,
|
459
|
+
) -> None:
|
460
|
+
all_operations: list[str] = []
|
461
|
+
|
462
|
+
for i, name in enumerate(sorted(self._operation_names)):
|
463
|
+
if i:
|
464
|
+
out.write('\n')
|
465
|
+
ops = self.gen_operation(
|
466
|
+
name,
|
467
|
+
)
|
468
|
+
all_operations.append(ops.name)
|
469
|
+
out.write(ops.src)
|
470
|
+
out.write('\n')
|
471
|
+
out.write('\n')
|
472
|
+
|
473
|
+
out.write('\n')
|
474
|
+
out.write('ALL_OPERATIONS: frozenset[_base.Operation] = frozenset([\n')
|
475
|
+
for n in sorted(all_operations):
|
476
|
+
out.write(f' {n},\n')
|
477
|
+
out.write('])\n')
|
478
|
+
|
479
|
+
#
|
480
|
+
|
481
|
+
def gen_module(self) -> str:
|
482
|
+
out = io.StringIO()
|
483
|
+
|
484
|
+
out.write(self.gen_preamble())
|
485
|
+
out.write('\n')
|
486
|
+
|
487
|
+
self.gen_all_shapes(
|
488
|
+
out,
|
489
|
+
unquoted_names=True,
|
490
|
+
)
|
491
|
+
|
492
|
+
if self._operation_names:
|
493
|
+
out.write('\n\n##\n\n\n')
|
494
|
+
|
495
|
+
self.gen_all_operations(out)
|
496
|
+
|
497
|
+
return out.getvalue()
|
File without changes
|