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