crossplane-function-pythonic 0.6.2__tar.gz → 0.6.3__tar.gz

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.
Files changed (141) hide show
  1. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3}/PKG-INFO +23 -26
  2. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3}/README.md +11 -9
  3. crossplane_function_pythonic-0.6.3/pyproject.toml +57 -0
  4. crossplane_function_pythonic-0.6.3/pyproject.toml.orig +58 -0
  5. crossplane_function_pythonic-0.6.3/src/crossplane/pythonic/__about__.py +8 -0
  6. crossplane_function_pythonic-0.6.3/src/crossplane/pythonic/__init__.py +25 -0
  7. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/__main__.py +1 -0
  8. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/auto_ready.py +89 -41
  9. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/command.py +43 -39
  10. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/composite.py +93 -68
  11. crossplane_function_pythonic-0.6.3/src/crossplane/pythonic/diff.py +880 -0
  12. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/function.py +185 -103
  13. crossplane_function_pythonic-0.6.3/src/crossplane/pythonic/grpc.py +173 -0
  14. crossplane_function_pythonic-0.6.3/src/crossplane/pythonic/main.py +37 -0
  15. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/packages.py +65 -42
  16. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/proto/v1/run_function_pb2.py +3 -3
  17. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/proto/v1/run_function_pb2_grpc.py +4 -4
  18. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/protobuf.py +281 -214
  19. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/render.py +408 -200
  20. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/version.py +2 -3
  21. crossplane_function_pythonic-0.6.2/.gitignore +0 -219
  22. crossplane_function_pythonic-0.6.2/LICENSE +0 -201
  23. crossplane_function_pythonic-0.6.2/crossplane/pythonic/__about__.py +0 -2
  24. crossplane_function_pythonic-0.6.2/crossplane/pythonic/__init__.py +0 -17
  25. crossplane_function_pythonic-0.6.2/crossplane/pythonic/grpc.py +0 -150
  26. crossplane_function_pythonic-0.6.2/crossplane/pythonic/main.py +0 -28
  27. crossplane_function_pythonic-0.6.2/examples/aks-cluster/README.md +0 -129
  28. crossplane_function_pythonic-0.6.2/examples/aks-cluster/aks/kubernetescluster.py +0 -29
  29. crossplane_function_pythonic-0.6.2/examples/aks-cluster/aks/resourcegroup.py +0 -12
  30. crossplane_function_pythonic-0.6.2/examples/aks-cluster/cluster-function-pythonic.yaml +0 -63
  31. crossplane_function_pythonic-0.6.2/examples/aks-cluster/composition.yaml +0 -24
  32. crossplane_function_pythonic-0.6.2/examples/aks-cluster/definition.yaml +0 -45
  33. crossplane_function_pythonic-0.6.2/examples/aks-cluster/install.sh +0 -13
  34. crossplane_function_pythonic-0.6.2/examples/aks-cluster/kustomization.yaml +0 -20
  35. crossplane_function_pythonic-0.6.2/examples/aks-cluster/providers.yaml +0 -14
  36. crossplane_function_pythonic-0.6.2/examples/aks-cluster/render.sh +0 -4
  37. crossplane_function_pythonic-0.6.2/examples/aks-cluster/xr.yaml +0 -16
  38. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/composition.yaml +0 -30
  39. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/definition.yaml +0 -26
  40. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/observed.yaml +0 -49
  41. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/render.sh +0 -5
  42. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/resources.yaml +0 -18
  43. crossplane_function_pythonic-0.6.2/examples/connection-details-composition/xr.yaml +0 -8
  44. crossplane_function_pythonic-0.6.2/examples/connections/composition.yaml +0 -39
  45. crossplane_function_pythonic-0.6.2/examples/connections/observed.yaml +0 -21
  46. crossplane_function_pythonic-0.6.2/examples/connections/render.sh +0 -7
  47. crossplane_function_pythonic-0.6.2/examples/connections/resources.yaml +0 -27
  48. crossplane_function_pythonic-0.6.2/examples/connections/xr.yaml +0 -8
  49. crossplane_function_pythonic-0.6.2/examples/eks-cluster/composition-v2.yaml +0 -138
  50. crossplane_function_pythonic-0.6.2/examples/eks-cluster/composition.yaml +0 -126
  51. crossplane_function_pythonic-0.6.2/examples/eks-cluster/definition.yaml +0 -47
  52. crossplane_function_pythonic-0.6.2/examples/eks-cluster/render-v2.sh +0 -3
  53. crossplane_function_pythonic-0.6.2/examples/eks-cluster/render.sh +0 -3
  54. crossplane_function_pythonic-0.6.2/examples/eks-cluster/xr.yaml +0 -13
  55. crossplane_function_pythonic-0.6.2/examples/filing-system/README.md +0 -13
  56. crossplane_function_pythonic-0.6.2/examples/filing-system/composition.yaml +0 -17
  57. crossplane_function_pythonic-0.6.2/examples/filing-system/definition.yaml +0 -21
  58. crossplane_function_pythonic-0.6.2/examples/filing-system/kustomization.yaml +0 -18
  59. crossplane_function_pythonic-0.6.2/examples/filing-system/runtime.yaml +0 -23
  60. crossplane_function_pythonic-0.6.2/examples/filing-system/vcluster.py +0 -38
  61. crossplane_function_pythonic-0.6.2/examples/filing-system/vcluster.yaml +0 -5
  62. crossplane_function_pythonic-0.6.2/examples/function-go-templating/conditions/composition.yaml +0 -31
  63. crossplane_function_pythonic-0.6.2/examples/function-go-templating/conditions/render.sh +0 -3
  64. crossplane_function_pythonic-0.6.2/examples/function-go-templating/conditions/xr.yaml +0 -5
  65. crossplane_function_pythonic-0.6.2/examples/function-go-templating/conditions/xrd.yaml +0 -25
  66. crossplane_function_pythonic-0.6.2/examples/function-go-templating/context/composition.yaml +0 -31
  67. crossplane_function_pythonic-0.6.2/examples/function-go-templating/context/render.sh +0 -3
  68. crossplane_function_pythonic-0.6.2/examples/function-go-templating/context/resources.yaml +0 -10
  69. crossplane_function_pythonic-0.6.2/examples/function-go-templating/context/xr.yaml +0 -5
  70. crossplane_function_pythonic-0.6.2/examples/function-go-templating/context/xrd.yaml +0 -25
  71. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/fromYaml/composition.yaml +0 -21
  72. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/fromYaml/render.sh +0 -3
  73. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/fromYaml/xr.yaml +0 -9
  74. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getComposedResource/composition.yaml +0 -28
  75. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getComposedResource/observed.yaml +0 -20
  76. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getComposedResource/render.sh +0 -3
  77. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getComposedResource/xr.yaml +0 -6
  78. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCompositeResource/composition.yaml +0 -23
  79. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCompositeResource/render.sh +0 -3
  80. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCompositeResource/xr.yaml +0 -7
  81. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCredentialData/composition.yaml +0 -27
  82. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCredentialData/render.sh +0 -3
  83. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCredentialData/resources.yaml +0 -9
  84. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getCredentialData/xr.yaml +0 -5
  85. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getResourceCondition/composition.yaml +0 -27
  86. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getResourceCondition/observed.yaml +0 -21
  87. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getResourceCondition/render.sh +0 -3
  88. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/getResourceCondition/xr.yaml +0 -12
  89. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/include/composition.yaml +0 -41
  90. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/include/render.sh +0 -3
  91. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/include/xr.yaml +0 -7
  92. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/toYaml/composition.yaml +0 -21
  93. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/toYaml/render.sh +0 -3
  94. crossplane_function_pythonic-0.6.2/examples/function-go-templating/functions/toYaml/xr.yaml +0 -12
  95. crossplane_function_pythonic-0.6.2/examples/function-go-templating/inline/composition.yaml +0 -35
  96. crossplane_function_pythonic-0.6.2/examples/function-go-templating/inline/render.sh +0 -3
  97. crossplane_function_pythonic-0.6.2/examples/function-go-templating/inline/xr.yaml +0 -6
  98. crossplane_function_pythonic-0.6.2/examples/function-go-templating/recursive/composition-real.yaml +0 -21
  99. crossplane_function_pythonic-0.6.2/examples/function-go-templating/recursive/composition.yaml +0 -23
  100. crossplane_function_pythonic-0.6.2/examples/function-go-templating/recursive/render.sh +0 -4
  101. crossplane_function_pythonic-0.6.2/examples/function-go-templating/recursive/xr.yaml +0 -8
  102. crossplane_function_pythonic-0.6.2/examples/function-go-templating/required-resources/composition.yaml +0 -29
  103. crossplane_function_pythonic-0.6.2/examples/function-go-templating/required-resources/render.sh +0 -3
  104. crossplane_function_pythonic-0.6.2/examples/function-go-templating/required-resources/resources.yaml +0 -14
  105. crossplane_function_pythonic-0.6.2/examples/function-go-templating/required-resources/xr.yaml +0 -6
  106. crossplane_function_pythonic-0.6.2/examples/function-sequencer/composition.yaml +0 -48
  107. crossplane_function_pythonic-0.6.2/examples/function-sequencer/observed.yaml +0 -27
  108. crossplane_function_pythonic-0.6.2/examples/function-sequencer/render.sh +0 -11
  109. crossplane_function_pythonic-0.6.2/examples/function-sequencer/sequencer.py +0 -12
  110. crossplane_function_pythonic-0.6.2/examples/function-sequencer/xr.yaml +0 -6
  111. crossplane_function_pythonic-0.6.2/examples/get-started-app/composition.yaml +0 -39
  112. crossplane_function_pythonic-0.6.2/examples/get-started-app/definition.yaml +0 -35
  113. crossplane_function_pythonic-0.6.2/examples/get-started-app/observed.yaml +0 -53
  114. crossplane_function_pythonic-0.6.2/examples/get-started-app/render.sh +0 -3
  115. crossplane_function_pythonic-0.6.2/examples/get-started-app/xr.yaml +0 -7
  116. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/composition.yaml +0 -17
  117. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/kustomization.yaml +0 -16
  118. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/render.sh +0 -3
  119. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/vcluster.py +0 -63
  120. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/xr.yaml +0 -5
  121. crossplane_function_pythonic-0.6.2/examples/helm-copy-secret/xrd.yaml +0 -21
  122. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/README.md +0 -35
  123. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/composition.yaml +0 -68
  124. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/definition.yaml +0 -39
  125. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/observed.yaml +0 -6
  126. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/provider-ec2.yaml +0 -20
  127. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/render.sh +0 -4
  128. crossplane_function_pythonic-0.6.2/examples/import-existing-vpc/xr.yaml +0 -10
  129. crossplane_function_pythonic-0.6.2/examples/inlined/composition.yaml +0 -17
  130. crossplane_function_pythonic-0.6.2/examples/inlined/definition.yaml +0 -32
  131. crossplane_function_pythonic-0.6.2/examples/inlined/render.sh +0 -3
  132. crossplane_function_pythonic-0.6.2/examples/inlined/xr.yaml +0 -10
  133. crossplane_function_pythonic-0.6.2/examples/run-all.sh +0 -3
  134. crossplane_function_pythonic-0.6.2/examples/usages-extra/composition.yaml +0 -26
  135. crossplane_function_pythonic-0.6.2/examples/usages-extra/observed.yaml +0 -6
  136. crossplane_function_pythonic-0.6.2/examples/usages-extra/render.sh +0 -3
  137. crossplane_function_pythonic-0.6.2/examples/usages-extra/resources.yaml +0 -7
  138. crossplane_function_pythonic-0.6.2/examples/usages-extra/xr.yaml +0 -6
  139. crossplane_function_pythonic-0.6.2/pyproject.toml +0 -153
  140. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/proto/v1/run_function.proto +0 -0
  141. {crossplane_function_pythonic-0.6.2 → crossplane_function_pythonic-0.6.3/src}/crossplane/pythonic/proto/v1/run_function_pb2.pyi +0 -0
@@ -1,28 +1,23 @@
1
- Metadata-Version: 2.5
1
+ Metadata-Version: 2.4
2
2
  Name: crossplane-function-pythonic
3
- Version: 0.6.2
3
+ Version: 0.6.3
4
4
  Summary: A Python centric Crossplane Function
5
- Project-URL: Documentation, https://github.com/crossplane-contrib/function-pythonic#readme
6
- Project-URL: Issues, https://github.com/crossplane-contrib/function-pythonic/issues
7
- Project-URL: Source, https://github.com/crossplane-contrib/function-pythonic
5
+ Author: Patrick J McNerthney
8
6
  Author-email: Patrick J McNerthney <pat@mcnerthney.com>
9
7
  License-Expression: Apache-2.0
10
- License-File: LICENSE
11
- Classifier: Development Status :: 4 - Beta
12
- Classifier: Programming Language :: Python
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3.14
16
- Requires-Python: <3.15,>=3.12
17
- Requires-Dist: grpcio==1.83.0
18
- Requires-Dist: inflect==7.5.0
8
+ Requires-Dist: grpcio==1.84.0
9
+ Requires-Dist: inflect==7.6.0
19
10
  Requires-Dist: kr8s==0.20.15
20
- Requires-Dist: protobuf==7.35.1
11
+ Requires-Dist: protobuf==7.36.2
21
12
  Requires-Dist: pyyaml==6.0.3
13
+ Requires-Dist: kopf==1.45.1 ; extra == 'packages'
14
+ Requires-Dist: pip==26.2.2 ; extra == 'pip-install'
15
+ Requires-Python: >=3.12, <3.16
16
+ Project-URL: Documentation, https://github.com/crossplane-contrib/function-pythonic#readme
17
+ Project-URL: Issues, https://github.com/crossplane-contrib/function-pythonic/issues
18
+ Project-URL: Source, https://github.com/crossplane-contrib/function-pythonic
22
19
  Provides-Extra: packages
23
- Requires-Dist: kopf==1.44.6; extra == 'packages'
24
20
  Provides-Extra: pip-install
25
- Requires-Dist: pip==26.2.1; extra == 'pip-install'
26
21
  Description-Content-Type: text/markdown
27
22
 
28
23
  # function-pythonic
@@ -213,7 +208,7 @@ message contents. For example, the following examples obtain the same value
213
208
  from the RunFunctionRequest message:
214
209
  ```python
215
210
  region = request.observed.composite.resource.spec.region
216
- region = request['observed']['composite']['resource']['spec']['region']
211
+ region = request["observed"]["composite"]["resource"]["spec"]["region"]
217
212
  ```
218
213
  Getting values from free form map and list values will not throw
219
214
  errors for keys that do not exist, but will return an unknown placeholder
@@ -232,13 +227,15 @@ When setting fields, all intermediary unknown placeholders will automatically
232
227
  be created. For example, this will create all items needed to set the
233
228
  region on the desired resource:
234
229
  ```python
235
- response.desired.resources.vpc.resource.spec.forProvider.region = 'us-east-1'
230
+ response.desired.resources.vpc.resource.spec.forProvider.region = "us-east-1"
236
231
  ```
237
232
  Calling a message or map will clear it and will set any provided key word
238
233
  arguments. For example, this will either create or clear the resource
239
234
  and then set its apiVersion and kind:
240
235
  ```python
241
- response.desired.resources.vpc.resource(kind='VPC', apiVersion='ec2.aws.crossplane.io/v1beta1')
236
+ response.desired.resources.vpc.resource(
237
+ kind="VPC", apiVersion="ec2.aws.crossplane.io/v1beta1"
238
+ )
242
239
  ```
243
240
  The following functions are provided to create Protobuf structures:
244
241
  | Function | Description |
@@ -257,12 +254,12 @@ The following items are supported in all the Protobuf Message wrapper classes: `
257
254
 
258
255
  To convert a Protobuf message to a string value, use either `str` or `format`.
259
256
  ```python
260
- yaml = str(request) # get the request as yaml
261
- yaml = format(request) # also get the request as yaml
262
- yaml = format(request, 'yaml') # yet another get the request as yaml
263
- json = format(request, 'json') # get the request as json
264
- json = format(request, 'jsonc') # get the request as json compact
265
- proto = format(request, 'protobuf') # get the request as a protobuf string
257
+ yaml = str(request) # get the request as yaml
258
+ yaml = format(request) # also get the request as yaml
259
+ yaml = format(request, "yaml") # yet another get the request as yaml
260
+ json = format(request, "json") # get the request as json
261
+ json = format(request, "jsonc") # get the request as json compact
262
+ proto = format(request, "protobuf") # get the request as a protobuf string
266
263
  ```
267
264
  ## Composite Composition
268
265
 
@@ -186,7 +186,7 @@ message contents. For example, the following examples obtain the same value
186
186
  from the RunFunctionRequest message:
187
187
  ```python
188
188
  region = request.observed.composite.resource.spec.region
189
- region = request['observed']['composite']['resource']['spec']['region']
189
+ region = request["observed"]["composite"]["resource"]["spec"]["region"]
190
190
  ```
191
191
  Getting values from free form map and list values will not throw
192
192
  errors for keys that do not exist, but will return an unknown placeholder
@@ -205,13 +205,15 @@ When setting fields, all intermediary unknown placeholders will automatically
205
205
  be created. For example, this will create all items needed to set the
206
206
  region on the desired resource:
207
207
  ```python
208
- response.desired.resources.vpc.resource.spec.forProvider.region = 'us-east-1'
208
+ response.desired.resources.vpc.resource.spec.forProvider.region = "us-east-1"
209
209
  ```
210
210
  Calling a message or map will clear it and will set any provided key word
211
211
  arguments. For example, this will either create or clear the resource
212
212
  and then set its apiVersion and kind:
213
213
  ```python
214
- response.desired.resources.vpc.resource(kind='VPC', apiVersion='ec2.aws.crossplane.io/v1beta1')
214
+ response.desired.resources.vpc.resource(
215
+ kind="VPC", apiVersion="ec2.aws.crossplane.io/v1beta1"
216
+ )
215
217
  ```
216
218
  The following functions are provided to create Protobuf structures:
217
219
  | Function | Description |
@@ -230,12 +232,12 @@ The following items are supported in all the Protobuf Message wrapper classes: `
230
232
 
231
233
  To convert a Protobuf message to a string value, use either `str` or `format`.
232
234
  ```python
233
- yaml = str(request) # get the request as yaml
234
- yaml = format(request) # also get the request as yaml
235
- yaml = format(request, 'yaml') # yet another get the request as yaml
236
- json = format(request, 'json') # get the request as json
237
- json = format(request, 'jsonc') # get the request as json compact
238
- proto = format(request, 'protobuf') # get the request as a protobuf string
235
+ yaml = str(request) # get the request as yaml
236
+ yaml = format(request) # also get the request as yaml
237
+ yaml = format(request, "yaml") # yet another get the request as yaml
238
+ json = format(request, "json") # get the request as json
239
+ json = format(request, "jsonc") # get the request as json compact
240
+ proto = format(request, "protobuf") # get the request as a protobuf string
239
241
  ```
240
242
  ## Composite Composition
241
243
 
@@ -0,0 +1,57 @@
1
+ [project]
2
+ name = "crossplane-function-pythonic"
3
+ version = "0.6.3"
4
+ description = "A Python centric Crossplane Function"
5
+ license = "Apache-2.0"
6
+ readme = "README.md"
7
+ requires-python = ">=3.12,<3.16"
8
+ dependencies = [
9
+ "grpcio==1.84.0",
10
+ "inflect==7.6.0",
11
+ "kr8s==0.20.15",
12
+ "protobuf==7.36.2",
13
+ "pyyaml==6.0.3",
14
+ ]
15
+
16
+ [[project.authors]]
17
+ name = "Patrick J McNerthney"
18
+ email = "pat@mcnerthney.com"
19
+
20
+ [project.optional-dependencies]
21
+ packages = ["kopf==1.45.1"]
22
+ pip-install = ["pip==26.2.2"]
23
+
24
+ [project.urls]
25
+ Documentation = "https://github.com/crossplane-contrib/function-pythonic#readme"
26
+ Issues = "https://github.com/crossplane-contrib/function-pythonic/issues"
27
+ Source = "https://github.com/crossplane-contrib/function-pythonic"
28
+
29
+ [project.scripts]
30
+ function-pythonic = "crossplane.pythonic.main:main"
31
+
32
+ [build-system]
33
+ requires = ["uv_build>=0.12.23,<0.13.0"]
34
+ build-backend = "uv_build"
35
+
36
+ [dependency-groups]
37
+ dev = [
38
+ "grpcio-tools>=1.84.0",
39
+ "poethepoet>=0.48.0",
40
+ "pytest>=9.1.1",
41
+ "pytest-asyncio>=1.4.0",
42
+ "pytest-cov>=7.1.0",
43
+ "ruff>=0.17.0",
44
+ ]
45
+
46
+ [tool.uv.build-backend]
47
+ module-name = "crossplane.pythonic"
48
+
49
+ [tool.poe.tasks]
50
+ generate-protoc = "python -m grpc_tools.protoc --proto_path=src --python_out=src --pyi_out=src --grpc_python_out=src crossplane/pythonic/proto/v1/run_function.proto"
51
+ test = "pytest tests -x --verbose --verbose --cov --cov-report=term --cov-report=html:reports"
52
+ test-protobuf = "pytest tests/test_protobuf_*.py -x --verbose --verbose --cov --cov-report=term --cov-report=html:reports"
53
+ test-ci = "pytest tests --verbose --verbose --junitxml=reports/pytest-junit.xml --cov --cov-report=term --cov-report=xml:reports/pytest-coverage.xml"
54
+
55
+ [tool.pytest.ini_options]
56
+ pythonpath = ["."]
57
+ filterwarnings = ["ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning:kr8s._vendored.asyncache"]
@@ -0,0 +1,58 @@
1
+ [project]
2
+ name = "crossplane-function-pythonic"
3
+ version = "0.6.3"
4
+ description = "A Python centric Crossplane Function"
5
+ license = "Apache-2.0"
6
+ readme = "README.md"
7
+ authors = [
8
+ { name = "Patrick J McNerthney", email = "pat@mcnerthney.com" }
9
+ ]
10
+ requires-python = ">=3.12,<3.16"
11
+ dependencies = [
12
+ "grpcio==1.84.0",
13
+ "inflect==7.6.0",
14
+ "kr8s==0.20.15",
15
+ "protobuf==7.36.2",
16
+ "pyyaml==6.0.3",
17
+ ]
18
+
19
+ [project.optional-dependencies]
20
+ packages = ["kopf==1.45.1"]
21
+ pip-install = ["pip==26.2.2"]
22
+
23
+ [project.urls]
24
+ Documentation = "https://github.com/crossplane-contrib/function-pythonic#readme"
25
+ Issues = "https://github.com/crossplane-contrib/function-pythonic/issues"
26
+ Source = "https://github.com/crossplane-contrib/function-pythonic"
27
+
28
+ [project.scripts]
29
+ function-pythonic = "crossplane.pythonic.main:main"
30
+
31
+ [build-system]
32
+ requires = ["uv_build>=0.12.23,<0.13.0"]
33
+ build-backend = "uv_build"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "grpcio-tools>=1.84.0",
38
+ "poethepoet>=0.48.0",
39
+ "pytest>=9.1.1",
40
+ "pytest-asyncio>=1.4.0",
41
+ "pytest-cov>=7.1.0",
42
+ "ruff>=0.17.0",
43
+ ]
44
+
45
+ [tool.uv.build-backend]
46
+ module-name = "crossplane.pythonic"
47
+
48
+ [tool.poe.tasks]
49
+ generate-protoc = "python -m grpc_tools.protoc --proto_path=src --python_out=src --pyi_out=src --grpc_python_out=src crossplane/pythonic/proto/v1/run_function.proto"
50
+ test = "pytest tests -x --verbose --verbose --cov --cov-report=term --cov-report=html:reports"
51
+ test-protobuf = "pytest tests/test_protobuf_*.py -x --verbose --verbose --cov --cov-report=term --cov-report=html:reports"
52
+ test-ci = "pytest tests --verbose --verbose --junitxml=reports/pytest-junit.xml --cov --cov-report=term --cov-report=xml:reports/pytest-coverage.xml"
53
+
54
+ [tool.pytest.ini_options]
55
+ pythonpath = ["."]
56
+ filterwarnings = [
57
+ "ignore:'asyncio.iscoroutinefunction' is deprecated:DeprecationWarning:kr8s._vendored.asyncache",
58
+ ]
@@ -0,0 +1,8 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version(
5
+ "crossplane-function-pythonic"
6
+ ) # Use the exact [project.name] from pyproject.toml
7
+ except PackageNotFoundError:
8
+ __version__ = "0.0.0"
@@ -0,0 +1,25 @@
1
+ from .composite import BaseComposite
2
+ from .protobuf import (
3
+ B64Decode,
4
+ B64Encode,
5
+ Json,
6
+ List,
7
+ Map,
8
+ Unknown,
9
+ Yaml,
10
+ YamlAll,
11
+ append,
12
+ )
13
+
14
+ __all__ = [
15
+ "B64Decode",
16
+ "B64Encode",
17
+ "BaseComposite",
18
+ "Json",
19
+ "List",
20
+ "Map",
21
+ "Unknown",
22
+ "Yaml",
23
+ "YamlAll",
24
+ "append",
25
+ ]
@@ -1,8 +1,9 @@
1
-
2
1
  def resource_ready(resource):
3
2
  if not resource.observed:
4
3
  return None
5
- return _checks.get((resource.observed.apiVersion, resource.observed.kind), _check_default).ready(resource)
4
+ return _checks.get(
5
+ (resource.observed.apiVersion, resource.observed.kind), _check_default
6
+ ).ready(resource)
6
7
 
7
8
 
8
9
  class ReadyCondition:
@@ -16,35 +17,42 @@ class ReadyCondition:
16
17
  return resource.status.notReadyCondition[ready.reason]
17
18
  return resource.status.notReadyCondition
18
19
 
20
+
19
21
  _checks = {}
20
22
  _check_default = ReadyCondition()
21
23
 
24
+
22
25
  class Check:
23
26
  @classmethod
24
27
  def __init_subclass__(cls, **kwargs):
25
28
  super().__init_subclass__(**kwargs)
26
- if hasattr(cls, 'apiVersion'):
29
+ if hasattr(cls, "apiVersion"):
27
30
  _checks[(cls.apiVersion, cls.__name__)] = cls()
28
31
 
29
32
  def ready(self, resource):
30
33
  raise NotImplementedError()
31
34
 
35
+
32
36
  class AlwaysReady(Check):
33
37
  def ready(self, resource):
34
38
  return resource.observed.metadata.name
35
39
 
36
40
 
37
41
  class ClusterRole(AlwaysReady):
38
- apiVersion = 'rbac.authorization.k8s.io/v1'
42
+ apiVersion = "rbac.authorization.k8s.io/v1"
43
+
39
44
 
40
45
  class ClusterRoleBinding(AlwaysReady):
41
- apiVersion = 'rbac.authorization.k8s.io/v1'
46
+ apiVersion = "rbac.authorization.k8s.io/v1"
47
+
42
48
 
43
49
  class ConfigMap(AlwaysReady):
44
- apiVersion = 'v1'
50
+ apiVersion = "v1"
51
+
45
52
 
46
53
  class CronJob(Check):
47
- apiVersion = 'batch/v1'
54
+ apiVersion = "batch/v1"
55
+
48
56
  def ready(self, resource):
49
57
  if resource.observed.spec.suspend and len(resource.observed.spec.suspend):
50
58
  return resource.observed.metadata.name
@@ -54,17 +62,21 @@ class CronJob(Check):
54
62
  return resource.observed.metadata.name
55
63
  if not resource.status.lastSuccessfulTime:
56
64
  return resource.status.lastSuccessfulTime
57
- if str(resource.status.lastSuccessfulTime) < str(resource.status.lastScheduleTime):
65
+ if str(resource.status.lastSuccessfulTime) < str(
66
+ resource.status.lastScheduleTime
67
+ ):
58
68
  return resource.status.successfulBeforeSchedule
59
69
  return resource.observed.metadata.name
60
70
 
71
+
61
72
  class DaemonSet(Check):
62
- apiVersion = 'apps/v1'
73
+ apiVersion = "apps/v1"
74
+
63
75
  def ready(self, resource):
64
76
  scheduled = resource.status.desiredNumberScheduled
65
77
  if not scheduled:
66
78
  return scheduled
67
- for field in ('numberReady', 'updatedNumberScheduled', 'numberAvailable'):
79
+ for field in ("numberReady", "updatedNumberScheduled", "numberAvailable"):
68
80
  value = resource.status[field]
69
81
  if not value:
70
82
  return value
@@ -72,16 +84,18 @@ class DaemonSet(Check):
72
84
  return resource.status[f"{field}NotScheduled"]
73
85
  return resource.observed.metadata.name
74
86
 
87
+
75
88
  class Deployment(Check):
76
- apiVersion = 'apps/v1'
89
+ apiVersion = "apps/v1"
90
+
77
91
  def ready(self, resource):
78
92
  replicas = resource.observed.spec.replicas or 1
79
- for field in ('updatedReplicas', 'availableReplicas'):
93
+ for field in ("updatedReplicas", "availableReplicas"):
80
94
  value = resource.status[field]
81
95
  if not value:
82
96
  return value
83
97
  if replicas != value:
84
- return resource.status[F"{field}NotReplicas"]
98
+ return resource.status[f"{field}NotReplicas"]
85
99
  available = resource.conditions.Available
86
100
  if not available:
87
101
  return resource.status.notAvailable
@@ -91,28 +105,39 @@ class Deployment(Check):
91
105
  return resource.status.notAvailable
92
106
  return resource.observed.metadata.name
93
107
 
108
+
94
109
  class HorizontalPodAutoscaler(Check):
95
- apiVersion = 'autoscaling/v2'
110
+ apiVersion = "autoscaling/v2"
111
+
96
112
  def ready(self, resource):
97
- for type in ('FailedGetScale', 'FailedUpdateScale', 'FailedGetResourceMetric', 'InvalidSelector'):
113
+ for type in (
114
+ "FailedGetScale",
115
+ "FailedUpdateScale",
116
+ "FailedGetResourceMetric",
117
+ "InvalidSelector",
118
+ ):
98
119
  if resource.conditions[type].status:
99
120
  return resource.status[f"is{type}"]
100
- for type in ('ScalingActive', 'ScalingLimited'):
121
+ for type in ("ScalingActive", "ScalingLimited"):
101
122
  if resource.conditions[type].status:
102
123
  return resource.observed.metadata.name
103
124
  return resource.status.notScalingActiveOrLimiited
104
125
 
126
+
105
127
  class Ingress(Check):
106
- apiVersion = 'networking.k8s.io/v1'
128
+ apiVersion = "networking.k8s.io/v1"
129
+
107
130
  def ready(self, resource):
108
131
  if not len(resource.status.loadBalancer.ingress):
109
132
  return resource.status.noLoadBalanceIngresses
110
133
  return resource.observed.metadata.name
111
134
 
135
+
112
136
  class Job(Check):
113
- apiVersion = 'batch/v1'
137
+ apiVersion = "batch/v1"
138
+
114
139
  def ready(self, resource):
115
- for type in ('Failed', 'Suspended'):
140
+ for type in ("Failed", "Suspended"):
116
141
  if resource.conditions[type].status:
117
142
  return resource.status[f"is{type}"]
118
143
  complete = resource.conditions.Complete
@@ -124,71 +149,94 @@ class Job(Check):
124
149
  return resource.status.notComplete
125
150
  return resource.observed.metadata.name
126
151
 
152
+
127
153
  class Namespace(AlwaysReady):
128
- apiVersion = 'v1'
154
+ apiVersion = "v1"
155
+
129
156
 
130
157
  class PersistentVolumeClaim(Check):
131
- apiVersion = 'v1'
158
+ apiVersion = "v1"
159
+
132
160
  def ready(self, resource):
133
- if resource.status.phase != 'Bound':
161
+ if resource.status.phase != "Bound":
134
162
  return resource.status.phaseNotBound
135
163
  return resource.observed.metadata.name
136
164
 
165
+
137
166
  class Pod(Check):
138
- apiVersion = 'v1'
167
+ apiVersion = "v1"
168
+
139
169
  def ready(self, resource):
140
- if resource.status.phase == 'Succeeded':
170
+ if resource.status.phase == "Succeeded":
171
+ return resource.observed.metadata.name
172
+ if (
173
+ resource.status.phase == "Running"
174
+ and resource.observed.spec.restartPolicy == "Always"
175
+ and resource.conditions.Ready.status
176
+ ):
141
177
  return resource.observed.metadata.name
142
- if resource.status.phase == 'Running':
143
- if resource.observed.spec.restartPolicy == 'Always':
144
- if resource.conditions.Ready.status:
145
- return resource.observed.metadata.name
146
178
  return resource.status.notSucceededOrRunning
147
179
 
180
+
148
181
  class ReplicaSet(Check):
149
- apiVersion = 'v1'
182
+ apiVersion = "v1"
183
+
150
184
  def ready(self, resource):
151
- if int(resource.status.observedGeneration) < int(resource.observed.metadata.generation):
185
+ if int(resource.status.observedGeneration) < int(
186
+ resource.observed.metadata.generation
187
+ ):
152
188
  return resource.status.priorObservedGeneration
153
189
  if resource.conditions.ReplicaFailure.status:
154
190
  if resource.conditions.ReplicaFailure.reason:
155
- return resource.status.isReplicaFailure[resource.conditions.ReplicaFailure.reason]
191
+ return resource.status.isReplicaFailure[
192
+ resource.conditions.ReplicaFailure.reason
193
+ ]
156
194
  return resource.status.isReplicaFailure
157
- if int(resource.status.availableReplicas) < int(resource.observed.spec.replicas or 1):
195
+ if int(resource.status.availableReplicas) < int(
196
+ resource.observed.spec.replicas or 1
197
+ ):
158
198
  return resource.status.tooFewavailableReplicas
159
199
  return resource.observed.metadata.name
160
200
 
201
+
161
202
  class Role(AlwaysReady):
162
- apiVersion = 'rbac.authorization.k8s.io/v1'
203
+ apiVersion = "rbac.authorization.k8s.io/v1"
204
+
163
205
 
164
206
  class RoleBinding(AlwaysReady):
165
- apiVersion = 'rbac.authorization.k8s.io/v1'
207
+ apiVersion = "rbac.authorization.k8s.io/v1"
208
+
166
209
 
167
210
  class Secret(AlwaysReady):
168
- apiVersion = 'v1'
211
+ apiVersion = "v1"
212
+
169
213
 
170
214
  class Service(Check):
171
- apiVersion = 'v1'
215
+ apiVersion = "v1"
216
+
172
217
  def ready(self, resource):
173
- if resource.observed.spec.type != 'LoadBalancer':
218
+ if resource.observed.spec.type != "LoadBalancer":
174
219
  return resource.observed.metadata.name
175
220
  if not len(resource.status.loadBalancer.ingress):
176
221
  return resource.status.noLoadBalancerIngresses
177
222
  return resource.observed.metadata.name
178
223
 
224
+
179
225
  class ServiceAccount(AlwaysReady):
180
- apiVersion = 'v1'
226
+ apiVersion = "v1"
227
+
181
228
 
182
229
  class StatefulSet(Check):
183
- apiVersion = 'apps/v1'
230
+ apiVersion = "apps/v1"
231
+
184
232
  def ready(self, resource):
185
233
  replicas = resource.observed.spec.replicas or 1
186
- for field in ('readyReplicas', 'currentReplicas'):
234
+ for field in ("readyReplicas", "currentReplicas"):
187
235
  value = resource.status[field]
188
236
  if not value:
189
237
  return value
190
238
  if replicas != value:
191
- return resource.status[F"{field}NotReplicas"]
239
+ return resource.status[f"{field}NotReplicas"]
192
240
  if resource.status.currentRevision != resource.status.updateRevision:
193
241
  return resource.status.currentRevisionNotUpdateReivsion
194
242
  return resource.observed.metadata.name