crossplane-function-pythonic 0.6.3__py3-none-any.whl → 0.7.0__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.
@@ -5,26 +5,31 @@ import importlib
5
5
  import inspect
6
6
  import logging
7
7
  import sys
8
+ import typing
8
9
 
9
10
  import grpc
11
+ from google.protobuf.struct_pb2 import Struct
12
+
13
+ from crossplane.pythonic.composite import BaseComposite
10
14
 
11
15
  from .. import pythonic
16
+ from . import protobuf
12
17
  from .proto.v1 import run_function_pb2 as fnv1
13
18
  from .proto.v1 import run_function_pb2_grpc as grpcv1
14
19
 
15
20
  logger = logging.getLogger(__name__)
16
21
 
17
22
 
18
- class FunctionRunner(grpcv1.FunctionRunnerService):
23
+ class FunctionRunner(grpcv1.FunctionRunnerServiceServicer):
19
24
  """A FunctionRunner handles gRPC RunFunctionRequests."""
20
25
 
21
- def __init__(self, renderUnknowns=False, crossplane_v1=False):
26
+ def __init__(self, renderUnknowns: bool = False, crossplane_v1: bool = False):
22
27
  """Create a new FunctionRunner."""
23
- self.renderUnknowns = renderUnknowns
24
- self.crossplane_v1 = crossplane_v1
25
- self.clazzes = {}
28
+ self.renderUnknowns: bool = renderUnknowns
29
+ self.crossplane_v1: bool = crossplane_v1
30
+ self.clazzes: dict[str, type[pythonic.BaseComposite]] = {}
26
31
 
27
- def invalidate_module(self, module):
32
+ def invalidate_module(self, module: str) -> None:
28
33
  ix = len(module)
29
34
  while ix > 0:
30
35
  module = module[:ix]
@@ -35,37 +40,46 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
35
40
  self.clazzes.clear()
36
41
 
37
42
  async def RunFunction(
38
- self, request: fnv1.RunFunctionRequest, _: grpc.aio.ServicerContext
43
+ self,
44
+ request: fnv1.RunFunctionRequest,
45
+ context: grpc.aio.ServicerContext | None = None,
39
46
  ) -> fnv1.RunFunctionResponse:
40
47
  try:
41
48
  return await self.run_function(request)
42
49
  except Exception as e: # noqa: BLE001
43
50
  return self.fatal(request, logger, "RunFunction", e)
44
51
 
45
- async def run_function(self, request):
46
- composite = request.observed.composite.resource
52
+ async def run_function(
53
+ self, request: fnv1.RunFunctionRequest
54
+ ) -> fnv1.RunFunctionResponse:
55
+ composite: Struct = request.observed.composite.resource
47
56
  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"])
57
+ name: list[str] = list(
58
+ reversed(str(composite["apiVersion"]).split("/")[0].split("."))
59
+ )
60
+ name.append(str(composite["kind"]))
61
+ name.append(str(typing.cast(Struct, composite["metadata"])["name"]))
51
62
  logger = logging.getLogger(".".join(name))
52
63
  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
- ):
64
+ if "spec" not in composite or str(
65
+ request.input["inlined"]
66
+ ) not in typing.cast(Struct, composite["spec"]):
57
67
  return self.fatal(
58
68
  request,
59
69
  logger,
60
70
  f"Missing inlined spec.{request.input['inlined']}",
61
71
  )
62
- composite = composite["spec"][request.input["inlined"]]
72
+ composite: str = str(
73
+ typing.cast(Struct, composite["spec"])[
74
+ str(request.input["inlined"])
75
+ ],
76
+ )
63
77
  else:
64
78
  if "composite" not in request.input:
65
79
  return self.fatal(
66
80
  request, logger, 'Missing composition input "composite"'
67
81
  )
68
- composite = request.input["composite"]
82
+ composite: str = str(request.input["composite"])
69
83
  operation = False
70
84
  else:
71
85
  logger = logging.getLogger("operation")
@@ -73,7 +87,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
73
87
  return self.fatal(
74
88
  request, logger, 'Missing operation input "composite"'
75
89
  )
76
- composite = request.input["composite"]
90
+ composite: str = str(request.input["composite"])
77
91
  operation = True
78
92
 
79
93
  # Ideally this is something the Function API provides
@@ -111,7 +125,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
111
125
  "Composite script does not have a BaseComposite class",
112
126
  )
113
127
  else:
114
- composite = composite.rsplit(".", 1)
128
+ composite: list[str] = composite.rsplit(".", 1)
115
129
  if len(composite) == 1:
116
130
  return self.fatal(
117
131
  request,
@@ -129,7 +143,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
129
143
  logger,
130
144
  f"{composite[0]} does not define: {composite[1]}",
131
145
  )
132
- composite = ".".join(composite)
146
+ composite: str = ".".join(composite)
133
147
  if not inspect.isclass(clazz):
134
148
  return self.fatal(request, logger, f"{composite} is not a class")
135
149
  if not issubclass(clazz, pythonic.BaseComposite):
@@ -147,7 +161,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
147
161
  logger = logging.getLogger(f"operation.{composite}")
148
162
 
149
163
  try:
150
- composite = clazz(self.crossplane_v1, request, logger)
164
+ composite: BaseComposite = clazz(self.crossplane_v1, request, logger)
151
165
  except Exception as e: # noqa: BLE001
152
166
  return self.fatal(request, logger, "Instantiate", e)
153
167
 
@@ -185,13 +199,19 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
185
199
  self.process_usages(composite)
186
200
  self.process_unknowns(composite)
187
201
  # Perform auto ready on all resources.
188
- for name, resource in composite.resources:
202
+ for _, resource in composite.resources:
189
203
  resource.ready # noqa: B018
190
204
  logger.info("Completed compose")
191
205
 
192
- return composite.response._message
206
+ return typing.cast(fnv1.RunFunctionResponse, composite.response._message)
193
207
 
194
- def fatal(self, request, logger, message, exception=None):
208
+ def fatal(
209
+ self,
210
+ request: fnv1.RunFunctionRequest,
211
+ logger: logging.Logger,
212
+ message: str,
213
+ exception: Exception | None = None,
214
+ ) -> fnv1.RunFunctionResponse:
195
215
  if exception:
196
216
  message += " exception"
197
217
  logger.exception(message)
@@ -213,8 +233,8 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
213
233
  ],
214
234
  )
215
235
 
216
- def get_schemas(self, step, composite):
217
- schemas = []
236
+ def get_schemas(self, step: protobuf.Value, composite: BaseComposite) -> list[str]:
237
+ schemas: list[str] = []
218
238
  for name, schema in composite.schemas:
219
239
  if len(schema.kind) and len(schema.apiVersion):
220
240
  s = pythonic.Map(kind=schema.kind, apiVersion=schema.apiVersion)
@@ -223,8 +243,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
223
243
  schemas.append(name)
224
244
  return schemas
225
245
 
226
- def get_requireds(self, step, composite):
227
- requireds = []
246
+ def get_requireds(
247
+ self, step: protobuf.Value, composite: BaseComposite
248
+ ) -> list[str]:
249
+ requireds: list[str] = []
228
250
  for name, required in composite.requireds:
229
251
  if len(required.kind) and len(required.apiVersion):
230
252
  r = pythonic.Map(kind=required.kind, apiVersion=required.apiVersion)
@@ -239,7 +261,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
239
261
  requireds.append(name)
240
262
  return requireds
241
263
 
242
- def process_usages(self, composite):
264
+ def process_usages(self, composite: BaseComposite) -> None:
243
265
  if self.crossplane_v1:
244
266
  apiVersion = "apiextensions.crossplane.io/v1beta1"
245
267
  else:
@@ -285,7 +307,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
285
307
  )
286
308
  for name, dependencies in resources.items():
287
309
  source = composite.resources[name]
288
- name = [resource.name, str(source.kind)]
310
+ name: list[str] = [str(resource.name), str(source.kind)]
289
311
  if source.metadata.namespace:
290
312
  name.append(str(source.metadata.namespace))
291
313
  name.append(str(source.observed.metadata.name))
@@ -322,7 +344,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
322
344
  usage.kind = "ClusterUsage"
323
345
  for key, dependencies in requireds.items():
324
346
  source = composite.requireds[key[0]][key[1]]
325
- name = [resource.name, str(source.kind)]
347
+ name: list[str] = [str(resource.name), str(source.kind)]
326
348
  if source.metadata.namespace:
327
349
  name.append(str(source.metadata.namespace))
328
350
  name.append(str(source.metadata.name))
@@ -358,10 +380,10 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
358
380
  else:
359
381
  usage.kind = "ClusterUsage"
360
382
 
361
- def process_unknowns(self, composite):
362
- unknownResources = []
363
- warningResources = []
364
- fatalResources = []
383
+ def process_unknowns(self, composite: BaseComposite) -> None:
384
+ unknownResources: list[str] = []
385
+ warningResources: list[str] = []
386
+ fatalResources: list[str] = []
365
387
  for name, resource in sorted(entry for entry in composite.resources):
366
388
  unknowns = resource.desired._getUnknowns
367
389
  if unknowns:
@@ -429,8 +451,8 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
429
451
  if result:
430
452
  result(reason, message)
431
453
 
432
- def trimFullName(self, name):
433
- name = name.split(".")
454
+ def trimFullName(self, name: str) -> str:
455
+ name: list[str] = name.split(".")
434
456
  for values in (
435
457
  ("request", "observed", "composite", "resource"),
436
458
  ("response", "desired", "composite", "resource"),
@@ -468,8 +490,7 @@ class FunctionRunner(grpcv1.FunctionRunnerService):
468
490
  return ".".join(name)
469
491
 
470
492
 
471
- def ordinal(ix):
472
- ix = int(ix)
493
+ def ordinal(ix: int) -> str:
473
494
  if 11 <= (ix % 100) <= 13:
474
495
  suffix = "th"
475
496
  else:
@@ -1,3 +1,4 @@
1
+ import argparse
1
2
  import asyncio
2
3
  import logging
3
4
  import os
@@ -23,7 +24,7 @@ class Command(command.Command):
23
24
  help = "Run function-pythonic gRPC server"
24
25
 
25
26
  @classmethod
26
- def add_parser_arguments(cls, parser):
27
+ def add_parser_arguments(cls, parser: argparse.ArgumentParser) -> None:
27
28
  cls.add_function_arguments(parser)
28
29
  parser.add_argument(
29
30
  "--address",
@@ -86,7 +87,7 @@ class Command(command.Command):
86
87
  help="Pip install command to install additional Python packages.",
87
88
  )
88
89
 
89
- def initialize(self):
90
+ def initialize(self) -> None:
90
91
  if not self.args.tls_certs_dir and not self.args.insecure:
91
92
  print(
92
93
  "Either --tls-certs-dir or --insecure must be specified",
@@ -114,7 +115,7 @@ class Command(command.Command):
114
115
  # enables read only volumes or mismatched uid volumes
115
116
  sys.dont_write_bytecode = True
116
117
 
117
- async def run(self):
118
+ async def run(self) -> None:
118
119
  grpc.aio.init_grpc_aio()
119
120
  grpc_runner = function.FunctionRunner(
120
121
  self.args.render_unknowns, self.args.crossplane_v1
@@ -21,12 +21,16 @@ from . import (
21
21
 
22
22
 
23
23
  def main():
24
- parser = argparse.ArgumentParser("Crossplane Function Pythonic")
25
- subparsers = parser.add_subparsers(title="Command", metavar="")
24
+ parser: argparse.ArgumentParser = argparse.ArgumentParser(
25
+ "Crossplane Function Pythonic"
26
+ )
27
+ subparsers: argparse._SubParsersAction[argparse.ArgumentParser] = (
28
+ parser.add_subparsers(title="Command", metavar="")
29
+ )
26
30
  grpc.Command.create(subparsers)
27
31
  render.Command.create(subparsers)
28
32
  version.Command.create(subparsers)
29
- args = parser.parse_args()
33
+ args: argparse.Namespace = parser.parse_args()
30
34
  if not hasattr(args, "command"):
31
35
  parser.print_help()
32
36
  sys.exit(1)
@@ -2,9 +2,13 @@ import base64
2
2
  import logging
3
3
  import pathlib
4
4
  import sys
5
+ import typing
5
6
 
7
+ import grpc
6
8
  import kopf
7
9
 
10
+ from . import function
11
+
8
12
  GRPC_SERVER = None
9
13
  GRPC_RUNNER = None
10
14
  PACKAGES_DIR = None
@@ -13,15 +17,15 @@ PACKAGE_LABELS = {PACKAGE_LABEL: kopf.PRESENT}
13
17
 
14
18
 
15
19
  def operator(
16
- grpc_server,
17
- grpc_runner,
18
- packages_configmaps,
19
- packages_secrets,
20
- packages_namespaces,
21
- packages_environmentconfigs,
22
- packages_compositions,
23
- packages_dir,
24
- ):
20
+ grpc_server: grpc.aio.Server,
21
+ grpc_runner: function.FunctionRunner,
22
+ packages_configmaps: bool,
23
+ packages_secrets: bool,
24
+ packages_namespaces: list[str],
25
+ packages_environmentconfigs: bool,
26
+ packages_compositions: bool,
27
+ packages_dir: str,
28
+ ) -> typing.Coroutine:
25
29
  logging.getLogger("kopf.objects").setLevel(logging.INFO)
26
30
  global GRPC_SERVER, GRPC_RUNNER, PACKAGES_DIR
27
31
  GRPC_SERVER = grpc_server
@@ -44,7 +48,7 @@ def operator(
44
48
  )
45
49
 
46
50
 
47
- def on_resource(group, version, plural):
51
+ def on_resource(group: str, version: str, plural: str):
48
52
  kopf.on.create(group, version, plural, labels=PACKAGE_LABELS)(create)
49
53
  kopf.on.resume(group, version, plural, labels=PACKAGE_LABELS)(create)
50
54
  kopf.on.update(group, version, plural, labels=PACKAGE_LABELS)(update)
@@ -52,25 +56,26 @@ def on_resource(group, version, plural):
52
56
 
53
57
 
54
58
  @kopf.on.startup()
55
- async def startup(settings, **_):
59
+ async def startup(settings: kopf.OperatorSettings, **_: typing.Any) -> None:
56
60
  settings.scanning.disabled = True
57
61
 
58
62
 
59
63
  @kopf.on.cleanup()
60
- async def cleanup(**_):
61
- await GRPC_SERVER.stop(5)
64
+ async def cleanup(**_: typing.Any) -> None:
65
+ if GRPC_SERVER:
66
+ await GRPC_SERVER.stop(5)
62
67
 
63
68
 
64
- async def create(resource, labels, body, logger, **_):
69
+ async def create(resource, labels, body, logger, **_: typing.Any):
65
70
  resource_create(resource, labels, "Created", body, logger)
66
71
 
67
72
 
68
- async def update(resource, labels, body, old, logger, **_):
73
+ async def update(resource, labels, body, old, logger, **_: typing.Any):
69
74
  resource_delete(resource, labels, "Removed", old, logger)
70
75
  resource_create(resource, labels, "Added", body, logger)
71
76
 
72
77
 
73
- async def delete(resource, labels, body, logger, **_):
78
+ async def delete(resource, labels, body, logger, **_: typing.Any):
74
79
  resource_delete(resource, labels, "Deleted", body, logger)
75
80
 
76
81
 
@@ -141,7 +146,8 @@ def package_create(resource, action, package_dir, package, logger):
141
146
  package_name.write_text(value)
142
147
  module, name = package_file_name(package_name)
143
148
  if module:
144
- GRPC_RUNNER.invalidate_module(name)
149
+ if GRPC_RUNNER:
150
+ GRPC_RUNNER.invalidate_module(name)
145
151
  logger.info(f"{action} module: {name}")
146
152
  else:
147
153
  logger.info(f"{action} file: {name}")
@@ -157,7 +163,8 @@ def package_delete(action, package_dir, package, logger):
157
163
  package_name.unlink(missing_ok=True)
158
164
  module, name = package_file_name(package_name)
159
165
  if module:
160
- GRPC_RUNNER.invalidate_module(name)
166
+ if GRPC_RUNNER:
167
+ GRPC_RUNNER.invalidate_module(name)
161
168
  logger.info(f"{action} module: {name}")
162
169
  else:
163
170
  logger.info(f"{action} file: {name}")
@@ -170,7 +177,8 @@ def package_delete(action, package_dir, package, logger):
170
177
  parent.rmdir()
171
178
  module = str(parent.relative_to(PACKAGES_DIR)).replace("/", ".")
172
179
  if module != ".":
173
- GRPC_RUNNER.invalidate_module(module)
180
+ if GRPC_RUNNER:
181
+ GRPC_RUNNER.invalidate_module(module)
174
182
  logger.info(f"{action} package: {module}")
175
183
  parent = parent.parent
176
184
  elif isinstance(value, dict):