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.
@@ -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, 'RunFunction', e)
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
- name = list(reversed(composite['apiVersion'].split('/')[0].split('.')))
47
- name.append(composite['kind'])
48
- name.append(composite['metadata']['name'])
49
- logger = logging.getLogger('.'.join(name))
50
-
51
- if 'inlined' in request.input and request.input['inlined']:
52
- if 'spec' not in composite or request.input['inlined'] not in composite['spec']:
53
- return self.fatal(request, logger, f"Missing inlined spec.{request.input['inlined']}")
54
- composite = composite['spec'][request.input['inlined']]
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
- if 'composite' not in request.input:
57
- return self.fatal(request, logger, 'Missing input "composite"')
58
- composite = request.input['composite']
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 'step' in request.input:
62
- step = request.input['step']
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 '\n' in composite:
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, 'Exec', e)
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 inspect.isclass(value) and issubclass(value, pythonic.BaseComposite) and value != pythonic.BaseComposite:
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(request, logger, 'Composite script has multiple BaseComposite classes')
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(request, logger, 'Composite script does not have a BaseComposite class')
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('.', 1)
114
+ composite = composite.rsplit(".", 1)
84
115
  if len(composite) == 1:
85
- return self.fatal(request, logger, f"Composite class name does not include module: {composite[0]}")
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, 'Import module', e)
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(request, logger, f"{composite[0]} does not define: {composite[1]}")
93
- composite = '.'.join(composite)
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(request, logger, f"{composite} is not a subclass of BaseComposite")
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, 'Instantiate', e)
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('Starting compose')
160
+ logger.info("Starting compose")
112
161
  else:
113
- logger.info(f"Starting compose, {ordinal(len(composite.context._pythonic))} step")
162
+ logger.info(
163
+ f"Starting compose, {ordinal(len(composite.context._pythonic))} step"
164
+ )
114
165
  else:
115
- logger.debug(f"Starting compose, {ordinal(len(composite.context._pythonic))} step, {ordinal(iteration)} pass")
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, 'Compose', e)
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('Completed compose')
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 += ' exception'
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 += ': ' + m
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 = 'apiextensions.crossplane.io/v1beta1'
244
+ apiVersion = "apiextensions.crossplane.io/v1beta1"
192
245
  else:
193
- apiVersion = 'protection.crossplane.io/v1beta1'
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 (len(name) > 5 and
208
- name[0] in ('request', 'response') and
209
- name[1] in ('observed', 'desired') and
210
- name[2] == 'resources' and
211
- name[4] == 'resource'
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(f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}")
216
- elif (len(name) > 5 and
217
- name[0] == 'request' and
218
- name[1] in ('required_resources', 'extra_resources') and
219
- name[3].startswith('items[') and name[3][-1] == ']' and
220
- name[4] == 'resource'
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(f"{'.'.join(destination.split('.')[5:])} = {'.'.join(name[5:])}")
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['_'.join(name)](apiVersion, 'Usage')
233
- usage.spec.reason = '\n'.join(dependencies)
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 source.metadata.namespace and source.metadata.namespace != composite.metadata.namespace:
244
- usage.spec.of.resourceRef.namespace = source.metadata.namespace
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 source.metadata.namespace and source.metadata.namespace != resource.metadata.namespace:
248
- usage.spec.of.resourceRef.namespace = source.metadata.namespace
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 = 'ClusterUsage'
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['_'.join(name)](apiVersion, 'Usage')
258
- usage.spec.reason = '\n'.join(dependencies)
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 source.metadata.namespace and source.metadata.namespace != composite.metadata.namespace:
269
- usage.spec.of.resourceRef.namespace = source.metadata.namespace
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 source.metadata.namespace and source.metadata.namespace != resource.metadata.namespace:
273
- usage.spec.of.resourceRef.namespace = source.metadata.namespace
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 = 'ClusterUsage'
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 (resource.unknownsFatal is None and composite.unknownsFatal):
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(f'Observed unknown: {destination} = {source}')
384
+ composite.logger.error(
385
+ f"Observed unknown: {destination} = {source}"
386
+ )
299
387
  elif warning:
300
- composite.logger.warning(f'Observed unknown: {destination} = {source}')
388
+ composite.logger.warning(
389
+ f"Observed unknown: {destination} = {source}"
390
+ )
301
391
  else:
302
- composite.logger.debug(f'Desired unknown: {destination} = {source}')
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 = 'FatalUnknowns'
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 = 'ObservedUnknowns'
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 = 'DesiredUnknowns'
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 = 'AllComposed'
331
- message = 'All resources are composed'
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
- ('request', 'observed', 'composite', 'resource'),
344
- ('response', 'desired', 'composite', 'resource'),
345
- ('request', 'observed', 'resources', None, 'resource'),
346
- ('response', 'desired', 'resources', None, 'resource'),
347
- ('request', 'required_resources', None, 'items', None, 'resource'),
348
- ('request', 'extra_resources', None, 'items', None, 'resource'),
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 value != name[ix]:
355
- if not name[ix].startswith(f"{values[iv]}[") or iv+1 >= len(values) or values[iv+1]:
356
- break
357
- continue
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 '.'.join(name)
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 = 'th'
474
+ suffix = "th"
380
475
  else:
381
- suffix = ['th', 'st', 'nd', 'rd', 'th'][min(ix % 10, 4)]
476
+ suffix = ["th", "st", "nd", "rd", "th"][min(ix % 10, 4)]
382
477
  return str(ix) + suffix
383
478
 
384
479