crossplane-function-pythonic 0.6.2__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.
@@ -1,4 +1,4 @@
1
-
1
+ import argparse
2
2
  import asyncio
3
3
  import logging
4
4
  import os
@@ -16,88 +16,98 @@ from . import (
16
16
  )
17
17
  from .proto.v1 import run_function_pb2_grpc as grpcv1
18
18
 
19
-
20
19
  logger = logging.getLogger(__name__)
21
20
 
22
21
 
23
22
  class Command(command.Command):
24
- name = 'grpc'
25
- help = 'Run function-pythonic gRPC server'
23
+ name = "grpc"
24
+ help = "Run function-pythonic gRPC server"
26
25
 
27
26
  @classmethod
28
- def add_parser_arguments(cls, parser):
27
+ def add_parser_arguments(cls, parser: argparse.ArgumentParser) -> None:
29
28
  cls.add_function_arguments(parser)
30
29
  parser.add_argument(
31
- '--address',
32
- default='0.0.0.0:9443',
33
- help='Address to listen on for gRPC connections, default: 0.0.0.0:9443',
30
+ "--address",
31
+ default="0.0.0.0:9443",
32
+ help="Address to listen on for gRPC connections, default: 0.0.0.0:9443",
34
33
  )
35
34
  parser.add_argument(
36
- '--tls-certs-dir',
37
- default=os.getenv('TLS_SERVER_CERTS_DIR'),
38
- metavar='DIRECTORY',
39
- help='Serve using TLS certificates.',
35
+ "--tls-certs-dir",
36
+ default=os.getenv("TLS_SERVER_CERTS_DIR"),
37
+ metavar="DIRECTORY",
38
+ help="Serve using TLS certificates.",
40
39
  )
41
40
  parser.add_argument(
42
- '--insecure',
43
- action='store_true',
44
- help='Run without mTLS credentials, --tls-certs-dir will be ignored.',
41
+ "--insecure",
42
+ action="store_true",
43
+ help="Run without mTLS credentials, --tls-certs-dir will be ignored.",
45
44
  )
46
45
  parser.add_argument(
47
- '--packages',
48
- action='store_true',
49
- dest='packages_configmaps',
50
- help='Discover python packages from function-pythonic ConfigMaps, deprecated use --packages-configmaps'
46
+ "--packages",
47
+ action="store_true",
48
+ dest="packages_configmaps",
49
+ help="Discover python packages from function-pythonic ConfigMaps, deprecated use --packages-configmaps",
51
50
  )
52
51
  parser.add_argument(
53
- '--packages-configmaps',
54
- action='store_true',
55
- help='Discover python packages from function-pythonic ConfigMaps.'
52
+ "--packages-configmaps",
53
+ action="store_true",
54
+ help="Discover python packages from function-pythonic ConfigMaps.",
56
55
  )
57
56
  parser.add_argument(
58
- '--packages-secrets',
59
- action='store_true',
60
- help='Also Discover python packages from function-pythonic Secrets.'
57
+ "--packages-secrets",
58
+ action="store_true",
59
+ help="Also Discover python packages from function-pythonic Secrets.",
61
60
  )
62
61
  parser.add_argument(
63
- '--packages-namespace',
64
- action='append',
62
+ "--packages-namespace",
63
+ action="append",
65
64
  default=[],
66
- metavar='NAMESPACE',
67
- help='Namespaces to discover function-pythonic ConfigMaps and Secrets in, default is cluster wide.',
65
+ metavar="NAMESPACE",
66
+ help="Namespaces to discover function-pythonic ConfigMaps and Secrets in, default is cluster wide.",
68
67
  )
69
68
  parser.add_argument(
70
- '--packages-environmentconfigs',
71
- action='store_true',
72
- help='Also Discover python packages from function-pythonic EnvironmentConfigs.'
69
+ "--packages-environmentconfigs",
70
+ action="store_true",
71
+ help="Also Discover python packages from function-pythonic EnvironmentConfigs.",
73
72
  )
74
73
  parser.add_argument(
75
- '--packages-compositions',
76
- action='store_true',
77
- help='Also Discover python packages from function-pythonic Compositions.'
74
+ "--packages-compositions",
75
+ action="store_true",
76
+ help="Also Discover python packages from function-pythonic Compositions.",
78
77
  )
79
78
  parser.add_argument(
80
- '--packages-dir',
81
- default='./pythonic-packages',
82
- metavar='DIRECTORY',
83
- help='Directory to store discovered function-pythonic ConfigMaps to, defaults "<cwd>/pythonic-packages"'
79
+ "--packages-dir",
80
+ default="./pythonic-packages",
81
+ metavar="DIRECTORY",
82
+ help='Directory to store discovered function-pythonic ConfigMaps to, defaults "<cwd>/pythonic-packages"',
84
83
  )
85
84
  parser.add_argument(
86
- '--pip-install',
87
- metavar='INSTALL',
88
- help='Pip install command to install additional Python packages.'
85
+ "--pip-install",
86
+ metavar="INSTALL",
87
+ help="Pip install command to install additional Python packages.",
89
88
  )
90
89
 
91
- def initialize(self):
90
+ def initialize(self) -> None:
92
91
  if not self.args.tls_certs_dir and not self.args.insecure:
93
- print('Either --tls-certs-dir or --insecure must be specified', file=sys.stderr)
92
+ print(
93
+ "Either --tls-certs-dir or --insecure must be specified",
94
+ file=sys.stderr,
95
+ )
94
96
  sys.exit(1)
95
- if (self.args.packages_environmentconfigs or self.args.packages_compositions) and self.args.packages_namespace:
96
- print('--packages-namespace cannot be used with --packages-environment-configs or --packages-compositions', file=sys.stderr)
97
+ if (
98
+ self.args.packages_environmentconfigs or self.args.packages_compositions
99
+ ) and self.args.packages_namespace:
100
+ print(
101
+ "--packages-namespace cannot be used with --packages-environment-configs or --packages-compositions",
102
+ file=sys.stderr,
103
+ )
97
104
  sys.exit(1)
98
105
  if self.args.pip_install:
99
106
  import pip._internal.cli.main
100
- pip._internal.cli.main.main(['install', '--user', *shlex.split(self.args.pip_install)])
107
+
108
+ pip._internal.cli.main.main(
109
+ ["install", "--user", *shlex.split(self.args.pip_install)]
110
+ )
101
111
 
102
112
  self.initialize_function()
103
113
  logger.info(f"Version: {__about__.__version__}")
@@ -105,9 +115,11 @@ class Command(command.Command):
105
115
  # enables read only volumes or mismatched uid volumes
106
116
  sys.dont_write_bytecode = True
107
117
 
108
- async def run(self):
118
+ async def run(self) -> None:
109
119
  grpc.aio.init_grpc_aio()
110
- grpc_runner = function.FunctionRunner(self.args.render_unknowns, self.args.crossplane_v1)
120
+ grpc_runner = function.FunctionRunner(
121
+ self.args.render_unknowns, self.args.crossplane_v1
122
+ )
111
123
  grpc_server = grpc.aio.server()
112
124
  grpcv1.add_FunctionRunnerServiceServicer_to_server(grpc_runner, grpc_server)
113
125
  if self.args.insecure:
@@ -117,33 +129,45 @@ class Command(command.Command):
117
129
  grpc_server.add_secure_port(
118
130
  self.args.address,
119
131
  grpc.ssl_server_credentials(
120
- private_key_certificate_chain_pairs=[(
121
- (certs / 'tls.key').read_bytes(),
122
- (certs / 'tls.crt').read_bytes(),
123
- )],
124
- root_certificates=(certs / 'ca.crt').read_bytes(),
132
+ private_key_certificate_chain_pairs=[
133
+ (
134
+ (certs / "tls.key").read_bytes(),
135
+ (certs / "tls.crt").read_bytes(),
136
+ )
137
+ ],
138
+ root_certificates=(certs / "ca.crt").read_bytes(),
125
139
  require_client_auth=True,
126
140
  ),
127
141
  )
128
142
  await grpc_server.start()
129
143
 
130
- if self.args.packages_configmaps or self.args.packages_secrets or self.args.packages_environmentconfigs or self.args.packages_compositions:
144
+ if (
145
+ self.args.packages_configmaps
146
+ or self.args.packages_secrets
147
+ or self.args.packages_environmentconfigs
148
+ or self.args.packages_compositions
149
+ ):
131
150
  from . import packages
151
+
132
152
  async with asyncio.TaskGroup() as tasks:
133
153
  tasks.create_task(grpc_server.wait_for_termination())
134
- tasks.create_task(packages.operator(
135
- grpc_server,
136
- grpc_runner,
137
- self.args.packages_configmaps,
138
- self.args.packages_secrets,
139
- self.args.packages_namespace,
140
- self.args.packages_environmentconfigs,
141
- self.args.packages_compositions,
142
- self.args.packages_dir,
143
- ))
154
+ tasks.create_task(
155
+ packages.operator(
156
+ grpc_server,
157
+ grpc_runner,
158
+ self.args.packages_configmaps,
159
+ self.args.packages_secrets,
160
+ self.args.packages_namespace,
161
+ self.args.packages_environmentconfigs,
162
+ self.args.packages_compositions,
163
+ self.args.packages_dir,
164
+ )
165
+ )
144
166
  else:
167
+
145
168
  def stop():
146
169
  asyncio.ensure_future(grpc_server.stop(5))
170
+
147
171
  loop = asyncio.get_event_loop()
148
172
  loop.add_signal_handler(signal.SIGINT, stop)
149
173
  loop.add_signal_handler(signal.SIGTERM, stop)
@@ -3,6 +3,15 @@
3
3
  import argparse
4
4
  import asyncio
5
5
  import sys
6
+ import warnings
7
+
8
+ # kr8s emits this at import time, so filter before importing it.
9
+ warnings.filterwarnings(
10
+ "ignore",
11
+ message="'asyncio.iscoroutinefunction' is deprecated",
12
+ category=DeprecationWarning,
13
+ module="kr8s._vendored.asyncache",
14
+ )
6
15
 
7
16
  from . import (
8
17
  grpc,
@@ -12,17 +21,21 @@ from . import (
12
21
 
13
22
 
14
23
  def main():
15
- parser = argparse.ArgumentParser('Crossplane Function Pythonic')
16
- 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
+ )
17
30
  grpc.Command.create(subparsers)
18
31
  render.Command.create(subparsers)
19
32
  version.Command.create(subparsers)
20
- args = parser.parse_args()
21
- if not hasattr(args, 'command'):
33
+ args: argparse.Namespace = parser.parse_args()
34
+ if not hasattr(args, "command"):
22
35
  parser.print_help()
23
36
  sys.exit(1)
24
37
  asyncio.run(args.command(args).run())
25
38
 
26
39
 
27
- if __name__ == '__main__':
40
+ if __name__ == "__main__":
28
41
  main()
@@ -1,42 +1,54 @@
1
-
2
1
  import base64
3
2
  import logging
4
3
  import pathlib
5
4
  import sys
5
+ import typing
6
6
 
7
+ import grpc
7
8
  import kopf
8
9
 
10
+ from . import function
9
11
 
10
12
  GRPC_SERVER = None
11
13
  GRPC_RUNNER = None
12
14
  PACKAGES_DIR = None
13
- PACKAGE_LABEL = 'function-pythonic.package'
15
+ PACKAGE_LABEL = "function-pythonic.package"
14
16
  PACKAGE_LABELS = {PACKAGE_LABEL: kopf.PRESENT}
15
17
 
16
18
 
17
- def operator(grpc_server, grpc_runner, packages_configmaps, packages_secrets, packages_namespaces, packages_environmentconfigs, packages_compositions, packages_dir):
18
- logging.getLogger('kopf.objects').setLevel(logging.INFO)
19
+ def operator(
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:
29
+ logging.getLogger("kopf.objects").setLevel(logging.INFO)
19
30
  global GRPC_SERVER, GRPC_RUNNER, PACKAGES_DIR
20
31
  GRPC_SERVER = grpc_server
21
32
  GRPC_RUNNER = grpc_runner
22
33
  PACKAGES_DIR = pathlib.Path(packages_dir).expanduser().resolve()
23
34
  sys.path.insert(0, str(PACKAGES_DIR))
24
35
  if packages_configmaps:
25
- on_resource('', 'v1', 'configmaps')
36
+ on_resource("", "v1", "configmaps")
26
37
  if packages_secrets:
27
- on_resource('', 'v1', 'secrets')
38
+ on_resource("", "v1", "secrets")
28
39
  if not packages_namespaces:
29
40
  if packages_environmentconfigs:
30
- on_resource('apiextensions.crossplane.io', 'v1beta1', 'environmentconfigs')
41
+ on_resource("apiextensions.crossplane.io", "v1beta1", "environmentconfigs")
31
42
  if packages_compositions:
32
- on_resource('apiextensions.crossplane.io', 'v1', 'compositions')
43
+ on_resource("apiextensions.crossplane.io", "v1", "compositions")
33
44
  return kopf.operator(
34
45
  standalone=True,
35
46
  clusterwide=not packages_namespaces,
36
47
  namespaces=packages_namespaces,
37
48
  )
38
49
 
39
- def on_resource(group, version, plural):
50
+
51
+ def on_resource(group: str, version: str, plural: str):
40
52
  kopf.on.create(group, version, plural, labels=PACKAGE_LABELS)(create)
41
53
  kopf.on.resume(group, version, plural, labels=PACKAGE_LABELS)(create)
42
54
  kopf.on.update(group, version, plural, labels=PACKAGE_LABELS)(update)
@@ -44,52 +56,66 @@ def on_resource(group, version, plural):
44
56
 
45
57
 
46
58
  @kopf.on.startup()
47
- async def startup(settings, **_):
59
+ async def startup(settings: kopf.OperatorSettings, **_: typing.Any) -> None:
48
60
  settings.scanning.disabled = True
49
61
 
50
62
 
51
63
  @kopf.on.cleanup()
52
- async def cleanup(**_):
53
- await GRPC_SERVER.stop(5)
64
+ async def cleanup(**_: typing.Any) -> None:
65
+ if GRPC_SERVER:
66
+ await GRPC_SERVER.stop(5)
54
67
 
55
68
 
56
- async def create(resource, labels, body, logger, **_):
57
- resource_create(resource, labels, 'Created', body, logger)
69
+ async def create(resource, labels, body, logger, **_: typing.Any):
70
+ resource_create(resource, labels, "Created", body, logger)
58
71
 
59
72
 
60
- async def update(resource, labels, body, old, logger, **_):
61
- resource_delete(resource, labels, 'Removed', old, logger)
62
- resource_create(resource, labels, 'Added', body, logger)
73
+ async def update(resource, labels, body, old, logger, **_: typing.Any):
74
+ resource_delete(resource, labels, "Removed", old, logger)
75
+ resource_create(resource, labels, "Added", body, logger)
63
76
 
64
77
 
65
- async def delete(resource, labels, body, logger, **_):
66
- resource_delete(resource, labels, 'Deleted', body, logger)
78
+ async def delete(resource, labels, body, logger, **_: typing.Any):
79
+ resource_delete(resource, labels, "Deleted", body, logger)
67
80
 
68
81
 
69
82
  def resource_create(resource, labels, action, body, logger):
70
83
  package_dir = resource_package_dir(resource, labels, logger)
71
84
  if not package_dir:
72
85
  return
73
- if resource.plural in ('configmaps', 'secrets', 'environmentconfigs'):
74
- package_create(resource, action, package_dir, body.get('data', {}), logger)
75
- elif resource.plural == 'compositions':
76
- for step in body.get('spec', {}).get('pipeline', []):
77
- input = step.get('input')
78
- if input and input.get('apiVersion') == 'pythonic.fn.crossplane.io/v1alpha1':
79
- package_create(resource, action, package_dir, input.get('packages', {}), logger)
86
+ if resource.plural in ("configmaps", "secrets", "environmentconfigs"):
87
+ package_create(resource, action, package_dir, body.get("data", {}), logger)
88
+ elif resource.plural == "compositions":
89
+ for step in body.get("spec", {}).get("pipeline", []):
90
+ input = step.get("input")
91
+ if (
92
+ input
93
+ and input.get("apiVersion") == "pythonic.fn.crossplane.io/v1alpha1"
94
+ ):
95
+ package_create(
96
+ resource, action, package_dir, input.get("packages", {}), logger
97
+ )
80
98
 
81
99
 
82
100
  def resource_delete(resource, labels, action, body, logger):
83
101
  package_dir = resource_package_dir(resource, labels, logger)
84
102
  if not package_dir:
85
103
  return
86
- if resource.plural in ('configmaps', 'secrets', 'environmentconfigs'):
87
- package_delete(action, package_dir, body.get('data', {}), logger)
88
- elif resource.plural == 'compositions':
89
- for step in body.get('spec', {}).get('pipeline', []):
90
- input = step.get('input')
91
- if input and input.get('apiVersion') == 'pythonic.fn.crossplane.io/v1alpha1':
92
- package_delete(action, package_dir, step.get('input', {}).get('packages', {}), logger)
104
+ if resource.plural in ("configmaps", "secrets", "environmentconfigs"):
105
+ package_delete(action, package_dir, body.get("data", {}), logger)
106
+ elif resource.plural == "compositions":
107
+ for step in body.get("spec", {}).get("pipeline", []):
108
+ input = step.get("input")
109
+ if (
110
+ input
111
+ and input.get("apiVersion") == "pythonic.fn.crossplane.io/v1alpha1"
112
+ ):
113
+ package_delete(
114
+ action,
115
+ package_dir,
116
+ step.get("input", {}).get("packages", {}),
117
+ logger,
118
+ )
93
119
 
94
120
 
95
121
  def resource_package_dir(resource, labels, logger):
@@ -99,10 +125,10 @@ def resource_package_dir(resource, labels, logger):
99
125
  logger.error(f"{PACKAGE_LABEL} label is missing")
100
126
  return None
101
127
  package_dir = PACKAGES_DIR
102
- if resource.plural in ('configmaps', 'secrets') and package:
103
- for segment in package.split('.'):
128
+ if resource.plural in ("configmaps", "secrets") and package:
129
+ for segment in package.split("."):
104
130
  if not segment.isidentifier():
105
- logger.error('Package has invalid package name: %s', package)
131
+ logger.error("Package has invalid package name: %s", package)
106
132
  return None
107
133
  package_dir = package_dir / segment
108
134
  return package_dir
@@ -114,13 +140,14 @@ def package_create(resource, action, package_dir, package, logger):
114
140
  package_name = package_dir / name
115
141
  if isinstance(value, str):
116
142
  package_name.parent.mkdir(parents=True, exist_ok=True)
117
- if resource.plural == 'secrets':
118
- package_name.write_bytes(base64.b64decode(value.encode('utf-8')))
143
+ if resource.plural == "secrets":
144
+ package_name.write_bytes(base64.b64decode(value.encode("utf-8")))
119
145
  else:
120
146
  package_name.write_text(value)
121
147
  module, name = package_file_name(package_name)
122
148
  if module:
123
- GRPC_RUNNER.invalidate_module(name)
149
+ if GRPC_RUNNER:
150
+ GRPC_RUNNER.invalidate_module(name)
124
151
  logger.info(f"{action} module: {name}")
125
152
  else:
126
153
  logger.info(f"{action} file: {name}")
@@ -136,20 +163,22 @@ def package_delete(action, package_dir, package, logger):
136
163
  package_name.unlink(missing_ok=True)
137
164
  module, name = package_file_name(package_name)
138
165
  if module:
139
- GRPC_RUNNER.invalidate_module(name)
166
+ if GRPC_RUNNER:
167
+ GRPC_RUNNER.invalidate_module(name)
140
168
  logger.info(f"{action} module: {name}")
141
169
  else:
142
170
  logger.info(f"{action} file: {name}")
143
171
  parent = package_name.parent
144
172
  while (
145
- parent.is_relative_to(PACKAGES_DIR)
146
- and parent.is_dir()
147
- and not list(parent.iterdir())
173
+ parent.is_relative_to(PACKAGES_DIR)
174
+ and parent.is_dir()
175
+ and not list(parent.iterdir())
148
176
  ):
149
177
  parent.rmdir()
150
- module = str(parent.relative_to(PACKAGES_DIR)).replace('/', '.')
151
- if module != '.':
152
- GRPC_RUNNER.invalidate_module(module)
178
+ module = str(parent.relative_to(PACKAGES_DIR)).replace("/", ".")
179
+ if module != ".":
180
+ if GRPC_RUNNER:
181
+ GRPC_RUNNER.invalidate_module(module)
153
182
  logger.info(f"{action} package: {module}")
154
183
  parent = parent.parent
155
184
  elif isinstance(value, dict):
@@ -158,13 +187,13 @@ def package_delete(action, package_dir, package, logger):
158
187
 
159
188
  def validate_entry(name, value, logger):
160
189
  if isinstance(value, str):
161
- if name.endswith('.py'):
190
+ if name.endswith(".py"):
162
191
  name = name[:-3]
163
192
  if not name.isidentifier():
164
193
  logger.error(f"Python module name is not an identifier: {name}")
165
194
  return False
166
195
  else:
167
- if '/' in name:
196
+ if "/" in name:
168
197
  logger.error(f"Python file name is not valid: {name}")
169
198
  return False
170
199
  elif isinstance(value, dict):
@@ -172,13 +201,15 @@ def validate_entry(name, value, logger):
172
201
  logger.error(f"Python package name is not an identifier: {name}")
173
202
  return False
174
203
  else:
175
- logger.error(f"Python package \"{name}\" value is not a valid type: {value.__class__}")
204
+ logger.error(
205
+ f'Python package "{name}" value is not a valid type: {value.__class__}'
206
+ )
176
207
  return False
177
208
  return True
178
209
 
179
210
 
180
211
  def package_file_name(package_name):
181
212
  name = str(package_name.relative_to(PACKAGES_DIR))
182
- if name.endswith('.py'):
183
- return True, name[:-3].replace('/', '.')
213
+ if name.endswith(".py"):
214
+ return True, name[:-3].replace("/", ".")
184
215
  return False, name
@@ -2,7 +2,7 @@
2
2
  # Generated by the protocol buffer compiler. DO NOT EDIT!
3
3
  # NO CHECKED-IN PROTOBUF GENCODE
4
4
  # source: crossplane/pythonic/proto/v1/run_function.proto
5
- # Protobuf Python Version: 6.31.1
5
+ # Protobuf Python Version: 7.35.1
6
6
  """Generated protocol buffer code."""
7
7
  from google.protobuf import descriptor as _descriptor
8
8
  from google.protobuf import descriptor_pool as _descriptor_pool
@@ -11,8 +11,8 @@ from google.protobuf import symbol_database as _symbol_database
11
11
  from google.protobuf.internal import builder as _builder
12
12
  _runtime_version.ValidateProtobufRuntimeVersion(
13
13
  _runtime_version.Domain.PUBLIC,
14
- 6,
15
- 31,
14
+ 7,
15
+ 35,
16
16
  1,
17
17
  '',
18
18
  'crossplane/pythonic/proto/v1/run_function.proto'