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