ominfra 0.0.0.dev199__py3-none-any.whl → 0.0.0.dev201__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|