crossplane-function-pythonic 0.6.2__py3-none-any.whl → 0.6.3__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.
- crossplane/pythonic/__about__.py +8 -2
- crossplane/pythonic/__init__.py +21 -13
- crossplane/pythonic/__main__.py +1 -0
- crossplane/pythonic/auto_ready.py +89 -41
- crossplane/pythonic/command.py +43 -39
- crossplane/pythonic/composite.py +93 -68
- crossplane/pythonic/diff.py +880 -0
- crossplane/pythonic/function.py +185 -103
- crossplane/pythonic/grpc.py +85 -62
- crossplane/pythonic/main.py +13 -4
- crossplane/pythonic/packages.py +65 -42
- crossplane/pythonic/proto/v1/run_function_pb2.py +3 -3
- crossplane/pythonic/proto/v1/run_function_pb2_grpc.py +4 -4
- crossplane/pythonic/protobuf.py +281 -214
- crossplane/pythonic/render.py +408 -200
- crossplane/pythonic/version.py +2 -3
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.6.3.dist-info}/METADATA +23 -26
- crossplane_function_pythonic-0.6.3.dist-info/RECORD +22 -0
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.6.3.dist-info}/WHEEL +1 -1
- {crossplane_function_pythonic-0.6.2.dist-info → crossplane_function_pythonic-0.6.3.dist-info}/entry_points.txt +1 -0
- crossplane_function_pythonic-0.6.2.dist-info/RECORD +0 -22
- crossplane_function_pythonic-0.6.2.dist-info/licenses/LICENSE +0 -201
crossplane/pythonic/function.py
CHANGED
|
@@ -7,9 +7,10 @@ import logging
|
|
|
7
7
|
import sys
|
|
8
8
|
|
|
9
9
|
import grpc
|
|
10
|
+
|
|
11
|
+
from .. import pythonic
|
|
10
12
|
from .proto.v1 import run_function_pb2 as fnv1
|
|
11
13
|
from .proto.v1 import run_function_pb2_grpc as grpcv1
|
|
12
|
-
from .. import pythonic
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
15
16
|
|
|
@@ -29,7 +30,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
29
30
|
module = module[:ix]
|
|
30
31
|
if module in sys.modules:
|
|
31
32
|
del sys.modules[module]
|
|
32
|
-
ix = module.rfind(
|
|
33
|
+
ix = module.rfind(".")
|
|
33
34
|
importlib.invalidate_caches()
|
|
34
35
|
self.clazzes.clear()
|
|
35
36
|
|
|
@@ -38,82 +39,117 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
38
39
|
) -> fnv1.RunFunctionResponse:
|
|
39
40
|
try:
|
|
40
41
|
return await self.run_function(request)
|
|
41
|
-
except Exception as e:
|
|
42
|
-
return self.fatal(request, logger,
|
|
42
|
+
except Exception as e: # noqa: BLE001
|
|
43
|
+
return self.fatal(request, logger, "RunFunction", e)
|
|
43
44
|
|
|
44
45
|
async def run_function(self, request):
|
|
45
46
|
composite = request.observed.composite.resource
|
|
46
47
|
if composite and composite.fields:
|
|
47
|
-
name = list(reversed(composite[
|
|
48
|
-
name.append(composite[
|
|
49
|
-
name.append(composite[
|
|
50
|
-
logger = logging.getLogger(
|
|
51
|
-
if
|
|
52
|
-
if
|
|
53
|
-
|
|
54
|
-
|
|
48
|
+
name = list(reversed(composite["apiVersion"].split("/")[0].split(".")))
|
|
49
|
+
name.append(composite["kind"])
|
|
50
|
+
name.append(composite["metadata"]["name"])
|
|
51
|
+
logger = logging.getLogger(".".join(name))
|
|
52
|
+
if "inlined" in request.input and request.input["inlined"]: # noqa: RUF019
|
|
53
|
+
if (
|
|
54
|
+
"spec" not in composite
|
|
55
|
+
or request.input["inlined"] not in composite["spec"]
|
|
56
|
+
):
|
|
57
|
+
return self.fatal(
|
|
58
|
+
request,
|
|
59
|
+
logger,
|
|
60
|
+
f"Missing inlined spec.{request.input['inlined']}",
|
|
61
|
+
)
|
|
62
|
+
composite = composite["spec"][request.input["inlined"]]
|
|
55
63
|
else:
|
|
56
|
-
if
|
|
57
|
-
return self.fatal(
|
|
58
|
-
|
|
64
|
+
if "composite" not in request.input:
|
|
65
|
+
return self.fatal(
|
|
66
|
+
request, logger, 'Missing composition input "composite"'
|
|
67
|
+
)
|
|
68
|
+
composite = request.input["composite"]
|
|
59
69
|
operation = False
|
|
60
70
|
else:
|
|
61
|
-
logger = logging.getLogger(
|
|
62
|
-
if
|
|
63
|
-
return self.fatal(
|
|
64
|
-
|
|
71
|
+
logger = logging.getLogger("operation")
|
|
72
|
+
if "composite" not in request.input:
|
|
73
|
+
return self.fatal(
|
|
74
|
+
request, logger, 'Missing operation input "composite"'
|
|
75
|
+
)
|
|
76
|
+
composite = request.input["composite"]
|
|
65
77
|
operation = True
|
|
66
78
|
|
|
67
79
|
# Ideally this is something the Function API provides
|
|
68
|
-
if
|
|
69
|
-
step = request.input[
|
|
80
|
+
if "step" in request.input:
|
|
81
|
+
step = request.input["step"]
|
|
70
82
|
else:
|
|
71
83
|
step = str(hash(composite))
|
|
72
84
|
|
|
73
85
|
clazz = self.clazzes.get(composite)
|
|
74
86
|
if not clazz:
|
|
75
|
-
if
|
|
87
|
+
if "\n" in composite:
|
|
76
88
|
module = Module()
|
|
77
89
|
try:
|
|
78
|
-
exec(composite, module.__dict__)
|
|
79
|
-
except Exception as e:
|
|
80
|
-
return self.fatal(request, logger,
|
|
90
|
+
exec(composite, module.__dict__) # noqa: S102
|
|
91
|
+
except Exception as e: # noqa: BLE001
|
|
92
|
+
return self.fatal(request, logger, "Exec", e)
|
|
81
93
|
for field in dir(module):
|
|
82
94
|
value = getattr(module, field)
|
|
83
|
-
if
|
|
95
|
+
if (
|
|
96
|
+
inspect.isclass(value)
|
|
97
|
+
and issubclass(value, pythonic.BaseComposite)
|
|
98
|
+
and value != pythonic.BaseComposite
|
|
99
|
+
):
|
|
84
100
|
if clazz:
|
|
85
|
-
return self.fatal(
|
|
101
|
+
return self.fatal(
|
|
102
|
+
request,
|
|
103
|
+
logger,
|
|
104
|
+
"Composite script has multiple BaseComposite classes",
|
|
105
|
+
)
|
|
86
106
|
clazz = value
|
|
87
107
|
if not clazz:
|
|
88
|
-
return self.fatal(
|
|
108
|
+
return self.fatal(
|
|
109
|
+
request,
|
|
110
|
+
logger,
|
|
111
|
+
"Composite script does not have a BaseComposite class",
|
|
112
|
+
)
|
|
89
113
|
else:
|
|
90
|
-
composite = composite.rsplit(
|
|
114
|
+
composite = composite.rsplit(".", 1)
|
|
91
115
|
if len(composite) == 1:
|
|
92
|
-
return self.fatal(
|
|
116
|
+
return self.fatal(
|
|
117
|
+
request,
|
|
118
|
+
logger,
|
|
119
|
+
f"Composite class name does not include module: {composite[0]}",
|
|
120
|
+
)
|
|
93
121
|
try:
|
|
94
122
|
module = importlib.import_module(composite[0])
|
|
95
|
-
except Exception as e:
|
|
96
|
-
return self.fatal(request, logger,
|
|
123
|
+
except Exception as e: # noqa: BLE001
|
|
124
|
+
return self.fatal(request, logger, "Import module", e)
|
|
97
125
|
clazz = getattr(module, composite[1], None)
|
|
98
126
|
if not clazz:
|
|
99
|
-
return self.fatal(
|
|
100
|
-
|
|
127
|
+
return self.fatal(
|
|
128
|
+
request,
|
|
129
|
+
logger,
|
|
130
|
+
f"{composite[0]} does not define: {composite[1]}",
|
|
131
|
+
)
|
|
132
|
+
composite = ".".join(composite)
|
|
101
133
|
if not inspect.isclass(clazz):
|
|
102
134
|
return self.fatal(request, logger, f"{composite} is not a class")
|
|
103
135
|
if not issubclass(clazz, pythonic.BaseComposite):
|
|
104
|
-
return self.fatal(
|
|
136
|
+
return self.fatal(
|
|
137
|
+
request,
|
|
138
|
+
logger,
|
|
139
|
+
f"{composite} is not a subclass of BaseComposite",
|
|
140
|
+
)
|
|
105
141
|
self.clazzes[composite] = clazz
|
|
106
142
|
|
|
107
143
|
if operation:
|
|
108
|
-
if
|
|
144
|
+
if "\n" in composite:
|
|
109
145
|
logger = logging.getLogger(f"operation.{clazz.__name__}")
|
|
110
146
|
else:
|
|
111
147
|
logger = logging.getLogger(f"operation.{composite}")
|
|
112
148
|
|
|
113
149
|
try:
|
|
114
150
|
composite = clazz(self.crossplane_v1, request, logger)
|
|
115
|
-
except Exception as e:
|
|
116
|
-
return self.fatal(request, logger,
|
|
151
|
+
except Exception as e: # noqa: BLE001
|
|
152
|
+
return self.fatal(request, logger, "Instantiate", e)
|
|
117
153
|
|
|
118
154
|
step = composite.context._pythonic[step]
|
|
119
155
|
iteration = int(step.iteration) + 1
|
|
@@ -121,18 +157,22 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
121
157
|
composite.context.iteration = iteration
|
|
122
158
|
if iteration == 1 and not logger.isEnabledFor(logging.DEBUG):
|
|
123
159
|
if len(composite.context._pythonic) == 1:
|
|
124
|
-
logger.info(
|
|
160
|
+
logger.info("Starting compose")
|
|
125
161
|
else:
|
|
126
|
-
logger.info(
|
|
162
|
+
logger.info(
|
|
163
|
+
f"Starting compose, {ordinal(len(composite.context._pythonic))} step"
|
|
164
|
+
)
|
|
127
165
|
else:
|
|
128
|
-
logger.debug(
|
|
166
|
+
logger.debug(
|
|
167
|
+
f"Starting compose, {ordinal(len(composite.context._pythonic))} step, {ordinal(iteration)} pass"
|
|
168
|
+
)
|
|
129
169
|
|
|
130
170
|
try:
|
|
131
171
|
result = composite.compose()
|
|
132
172
|
if asyncio.iscoroutine(result):
|
|
133
173
|
await result
|
|
134
|
-
except Exception as e:
|
|
135
|
-
return self.fatal(request, logger,
|
|
174
|
+
except Exception as e: # noqa: BLE001
|
|
175
|
+
return self.fatal(request, logger, "Compose", e)
|
|
136
176
|
|
|
137
177
|
schemas = self.get_schemas(step, composite)
|
|
138
178
|
requireds = self.get_requireds(step, composite)
|
|
@@ -146,19 +186,19 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
146
186
|
self.process_unknowns(composite)
|
|
147
187
|
# Perform auto ready on all resources.
|
|
148
188
|
for name, resource in composite.resources:
|
|
149
|
-
resource.ready
|
|
150
|
-
logger.info(
|
|
189
|
+
resource.ready # noqa: B018
|
|
190
|
+
logger.info("Completed compose")
|
|
151
191
|
|
|
152
192
|
return composite.response._message
|
|
153
193
|
|
|
154
194
|
def fatal(self, request, logger, message, exception=None):
|
|
155
195
|
if exception:
|
|
156
|
-
message +=
|
|
196
|
+
message += " exception"
|
|
157
197
|
logger.exception(message)
|
|
158
198
|
m = str(exception)
|
|
159
199
|
if not m:
|
|
160
200
|
m = exception.__class__.__name__
|
|
161
|
-
message +=
|
|
201
|
+
message += ": " + m
|
|
162
202
|
else:
|
|
163
203
|
logger.error(message)
|
|
164
204
|
return fnv1.RunFunctionResponse(
|
|
@@ -170,7 +210,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
170
210
|
severity=fnv1.SEVERITY_FATAL,
|
|
171
211
|
message=message,
|
|
172
212
|
)
|
|
173
|
-
]
|
|
213
|
+
],
|
|
174
214
|
)
|
|
175
215
|
|
|
176
216
|
def get_schemas(self, step, composite):
|
|
@@ -201,9 +241,9 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
201
241
|
|
|
202
242
|
def process_usages(self, composite):
|
|
203
243
|
if self.crossplane_v1:
|
|
204
|
-
apiVersion =
|
|
244
|
+
apiVersion = "apiextensions.crossplane.io/v1beta1"
|
|
205
245
|
else:
|
|
206
|
-
apiVersion =
|
|
246
|
+
apiVersion = "protection.crossplane.io/v1beta1"
|
|
207
247
|
for _, resource in sorted(entry for entry in composite.resources):
|
|
208
248
|
dependencies = resource.desired._getDependencies
|
|
209
249
|
if dependencies:
|
|
@@ -216,34 +256,41 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
216
256
|
resources = {}
|
|
217
257
|
requireds = {}
|
|
218
258
|
for destination, source in sorted(dependencies.items()):
|
|
219
|
-
name = source.split(
|
|
220
|
-
if (
|
|
221
|
-
name
|
|
222
|
-
name[
|
|
223
|
-
name[
|
|
224
|
-
name[
|
|
259
|
+
name = source.split(".")
|
|
260
|
+
if (
|
|
261
|
+
len(name) > 5
|
|
262
|
+
and name[0] in ("request", "response")
|
|
263
|
+
and name[1] in ("observed", "desired")
|
|
264
|
+
and name[2] == "resources"
|
|
265
|
+
and name[4] == "resource"
|
|
225
266
|
):
|
|
226
267
|
if name[3] not in resources:
|
|
227
268
|
resources[name[3]] = []
|
|
228
|
-
resources[name[3]].append(
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
name
|
|
233
|
-
name[
|
|
269
|
+
resources[name[3]].append(
|
|
270
|
+
f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}"
|
|
271
|
+
)
|
|
272
|
+
elif (
|
|
273
|
+
len(name) > 5
|
|
274
|
+
and name[0] == "request"
|
|
275
|
+
and name[1] in ("required_resources", "extra_resources")
|
|
276
|
+
and name[3].startswith("items[")
|
|
277
|
+
and name[3][-1] == "]"
|
|
278
|
+
and name[4] == "resource"
|
|
234
279
|
):
|
|
235
280
|
key = (name[2], int(name[3][6:-1]))
|
|
236
281
|
if key not in requireds:
|
|
237
282
|
requireds[key] = []
|
|
238
|
-
requireds[key].append(
|
|
283
|
+
requireds[key].append(
|
|
284
|
+
f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}"
|
|
285
|
+
)
|
|
239
286
|
for name, dependencies in resources.items():
|
|
240
287
|
source = composite.resources[name]
|
|
241
288
|
name = [resource.name, str(source.kind)]
|
|
242
289
|
if source.metadata.namespace:
|
|
243
290
|
name.append(str(source.metadata.namespace))
|
|
244
291
|
name.append(str(source.observed.metadata.name))
|
|
245
|
-
usage = composite.resources[
|
|
246
|
-
usage.spec.reason =
|
|
292
|
+
usage = composite.resources["_".join(name)](apiVersion, "Usage")
|
|
293
|
+
usage.spec.reason = "\n".join(dependencies)
|
|
247
294
|
usage.spec.replayDeletion = True
|
|
248
295
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
249
296
|
usage.spec.by.kind = resource.kind
|
|
@@ -253,22 +300,34 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
253
300
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
254
301
|
if not self.crossplane_v1:
|
|
255
302
|
if composite.metadata.namespace:
|
|
256
|
-
if
|
|
257
|
-
|
|
303
|
+
if (
|
|
304
|
+
source.metadata.namespace
|
|
305
|
+
and source.metadata.namespace
|
|
306
|
+
!= composite.metadata.namespace
|
|
307
|
+
):
|
|
308
|
+
usage.spec.of.resourceRef.namespace = (
|
|
309
|
+
source.metadata.namespace
|
|
310
|
+
)
|
|
258
311
|
elif resource.metadata.namespace:
|
|
259
312
|
usage.metadata.namespace = resource.metadata.namespace
|
|
260
|
-
if
|
|
261
|
-
|
|
313
|
+
if (
|
|
314
|
+
source.metadata.namespace
|
|
315
|
+
and source.metadata.namespace
|
|
316
|
+
!= resource.metadata.namespace
|
|
317
|
+
):
|
|
318
|
+
usage.spec.of.resourceRef.namespace = (
|
|
319
|
+
source.metadata.namespace
|
|
320
|
+
)
|
|
262
321
|
else:
|
|
263
|
-
usage.kind =
|
|
322
|
+
usage.kind = "ClusterUsage"
|
|
264
323
|
for key, dependencies in requireds.items():
|
|
265
324
|
source = composite.requireds[key[0]][key[1]]
|
|
266
325
|
name = [resource.name, str(source.kind)]
|
|
267
326
|
if source.metadata.namespace:
|
|
268
327
|
name.append(str(source.metadata.namespace))
|
|
269
328
|
name.append(str(source.metadata.name))
|
|
270
|
-
usage = composite.resources[
|
|
271
|
-
usage.spec.reason =
|
|
329
|
+
usage = composite.resources["_".join(name)](apiVersion, "Usage")
|
|
330
|
+
usage.spec.reason = "\n".join(dependencies)
|
|
272
331
|
usage.spec.replayDeletion = True
|
|
273
332
|
usage.spec.by.apiVersion = resource.apiVersion
|
|
274
333
|
usage.spec.by.kind = resource.kind
|
|
@@ -278,14 +337,26 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
278
337
|
usage.spec.of.resourceRef.name = source.observed.metadata.name
|
|
279
338
|
if not self.crossplane_v1:
|
|
280
339
|
if composite.metadata.namespace:
|
|
281
|
-
if
|
|
282
|
-
|
|
340
|
+
if (
|
|
341
|
+
source.metadata.namespace
|
|
342
|
+
and source.metadata.namespace
|
|
343
|
+
!= composite.metadata.namespace
|
|
344
|
+
):
|
|
345
|
+
usage.spec.of.resourceRef.namespace = (
|
|
346
|
+
source.metadata.namespace
|
|
347
|
+
)
|
|
283
348
|
elif resource.metadata.namespace:
|
|
284
349
|
usage.metadata.namespace = resource.metadata.namespace
|
|
285
|
-
if
|
|
286
|
-
|
|
350
|
+
if (
|
|
351
|
+
source.metadata.namespace
|
|
352
|
+
and source.metadata.namespace
|
|
353
|
+
!= resource.metadata.namespace
|
|
354
|
+
):
|
|
355
|
+
usage.spec.of.resourceRef.namespace = (
|
|
356
|
+
source.metadata.namespace
|
|
357
|
+
)
|
|
287
358
|
else:
|
|
288
|
-
usage.kind =
|
|
359
|
+
usage.kind = "ClusterUsage"
|
|
289
360
|
|
|
290
361
|
def process_unknowns(self, composite):
|
|
291
362
|
unknownResources = []
|
|
@@ -300,7 +371,9 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
300
371
|
if resource.observed:
|
|
301
372
|
warningResources.append(name)
|
|
302
373
|
warning = True
|
|
303
|
-
if resource.unknownsFatal or (
|
|
374
|
+
if resource.unknownsFatal or (
|
|
375
|
+
resource.unknownsFatal is None and composite.unknownsFatal
|
|
376
|
+
):
|
|
304
377
|
fatalResources.append(name)
|
|
305
378
|
fatal = True
|
|
306
379
|
if composite.logger.isEnabledFor(logging.DEBUG):
|
|
@@ -308,11 +381,17 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
308
381
|
destination = self.trimFullName(destination)
|
|
309
382
|
source = self.trimFullName(source)
|
|
310
383
|
if fatal:
|
|
311
|
-
composite.logger.error(
|
|
384
|
+
composite.logger.error(
|
|
385
|
+
f"Observed unknown: {destination} = {source}"
|
|
386
|
+
)
|
|
312
387
|
elif warning:
|
|
313
|
-
composite.logger.warning(
|
|
388
|
+
composite.logger.warning(
|
|
389
|
+
f"Observed unknown: {destination} = {source}"
|
|
390
|
+
)
|
|
314
391
|
else:
|
|
315
|
-
composite.logger.debug(
|
|
392
|
+
composite.logger.debug(
|
|
393
|
+
f"Desired unknown: {destination} = {source}"
|
|
394
|
+
)
|
|
316
395
|
if resource.observed:
|
|
317
396
|
resource.desired._patchUnknowns(resource.observed)
|
|
318
397
|
elif self.renderUnknowns:
|
|
@@ -322,26 +401,26 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
322
401
|
|
|
323
402
|
if fatalResources:
|
|
324
403
|
level = composite.logger.error
|
|
325
|
-
reason =
|
|
404
|
+
reason = "FatalUnknowns"
|
|
326
405
|
message = f"Observed resources with unknowns: {','.join(fatalResources)}"
|
|
327
406
|
status = False
|
|
328
407
|
result = composite.results.fatal
|
|
329
408
|
elif warningResources:
|
|
330
409
|
level = composite.logger.warning
|
|
331
|
-
reason =
|
|
410
|
+
reason = "ObservedUnknowns"
|
|
332
411
|
message = f"Observed resources with unknowns: {','.join(warningResources)}"
|
|
333
412
|
status = False
|
|
334
413
|
result = composite.results.warning
|
|
335
414
|
elif unknownResources:
|
|
336
415
|
level = composite.logger.info
|
|
337
|
-
reason =
|
|
416
|
+
reason = "DesiredUnknowns"
|
|
338
417
|
message = f"Desired resources with unknowns: {','.join(unknownResources)}"
|
|
339
418
|
status = False
|
|
340
419
|
result = composite.results.info
|
|
341
420
|
else:
|
|
342
421
|
level = None
|
|
343
|
-
reason =
|
|
344
|
-
message =
|
|
422
|
+
reason = "AllComposed"
|
|
423
|
+
message = "All resources are composed"
|
|
345
424
|
status = True
|
|
346
425
|
result = None
|
|
347
426
|
if not composite.logger.isEnabledFor(logging.DEBUG) and level:
|
|
@@ -351,23 +430,26 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
351
430
|
result(reason, message)
|
|
352
431
|
|
|
353
432
|
def trimFullName(self, name):
|
|
354
|
-
name = name.split(
|
|
433
|
+
name = name.split(".")
|
|
355
434
|
for values in (
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
435
|
+
("request", "observed", "composite", "resource"),
|
|
436
|
+
("response", "desired", "composite", "resource"),
|
|
437
|
+
("request", "observed", "resources", None, "resource"),
|
|
438
|
+
("response", "desired", "resources", None, "resource"),
|
|
439
|
+
("request", "required_resources", None, "items", None, "resource"),
|
|
440
|
+
("request", "extra_resources", None, "items", None, "resource"),
|
|
362
441
|
):
|
|
363
442
|
if len(values) < len(name):
|
|
364
443
|
ix = 0
|
|
365
444
|
for iv, value in enumerate(values):
|
|
366
|
-
if value:
|
|
367
|
-
if
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
445
|
+
if value and value != name[ix]:
|
|
446
|
+
if (
|
|
447
|
+
not name[ix].startswith(f"{value}[")
|
|
448
|
+
or iv + 1 >= len(values)
|
|
449
|
+
or values[iv + 1]
|
|
450
|
+
):
|
|
451
|
+
break
|
|
452
|
+
continue
|
|
371
453
|
ix += 1
|
|
372
454
|
else:
|
|
373
455
|
ix = 0
|
|
@@ -376,22 +458,22 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
|
|
|
376
458
|
if value == name[ix]:
|
|
377
459
|
del name[ix]
|
|
378
460
|
elif ix:
|
|
379
|
-
name[ix-1] += name[ix][len(value):]
|
|
461
|
+
name[ix - 1] += name[ix][len(value) :]
|
|
380
462
|
del name[ix]
|
|
381
463
|
else:
|
|
382
|
-
name[ix] = name[ix][len(value):]
|
|
464
|
+
name[ix] = name[ix][len(value) :]
|
|
383
465
|
else:
|
|
384
466
|
ix += 1
|
|
385
467
|
break
|
|
386
|
-
return
|
|
468
|
+
return ".".join(name)
|
|
387
469
|
|
|
388
470
|
|
|
389
471
|
def ordinal(ix):
|
|
390
472
|
ix = int(ix)
|
|
391
473
|
if 11 <= (ix % 100) <= 13:
|
|
392
|
-
suffix =
|
|
474
|
+
suffix = "th"
|
|
393
475
|
else:
|
|
394
|
-
suffix = [
|
|
476
|
+
suffix = ["th", "st", "nd", "rd", "th"][min(ix % 10, 4)]
|
|
395
477
|
return str(ix) + suffix
|
|
396
478
|
|
|
397
479
|
|