Skip to content

Commit f3066d6

Browse files
committed
Add support for async compose and operatoe functions.
Signed-off-by: Bob Haddleton <bob.haddleton@nokia.com>
1 parent a875b3e commit f3066d6

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ spec:
110110

111111
For more complex operations, see the [operation examples](example/operation/).
112112

113+
## Async Functions
114+
115+
`function-python` supports async functions for compositions and operations. Use `async def compose()`
116+
and `async def operate()` to define async functions. `async` should be used if the script/function
117+
is making network or other potentially blocking IO calls `async`. This allows
118+
`function-python` to serve multiple requests concurrently without blocking.
119+
113120
## Usage Notes
114121

115122
`function-python` is best for very simple cases. If writing Python inline of

function/fn.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""A Crossplane composition function."""
22

33
import importlib.util
4+
import inspect
45
import types
56

67
import grpc
@@ -44,10 +45,16 @@ async def RunFunction(
4445
response.fatal(rsp, msg)
4546
case (True, False):
4647
log.debug("running composition function")
47-
script.compose(req, rsp)
48+
if inspect.iscoroutinefunction(script.compose):
49+
await script.compose(req, rsp)
50+
else:
51+
script.compose(req, rsp)
4852
case (False, True):
4953
log.debug("running operation function")
50-
script.operate(req, rsp)
54+
if inspect.iscoroutinefunction(script.operate):
55+
await script.operate(req, rsp)
56+
else:
57+
script.operate(req, rsp)
5158
case (False, False):
5259
msg = "script must define a compose or operate function"
5360
log.debug(msg)

tests/test_fn.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse):
2424
})
2525
"""
2626

27+
async_composition_script = """
28+
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
29+
30+
async def compose(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse):
31+
rsp.desired.resources["bucket"].resource.update({
32+
"apiVersion": "s3.aws.upbound.io/v1beta2",
33+
"kind": "Bucket",
34+
"spec": {
35+
"forProvider": {
36+
"region": "us-east-1"
37+
}
38+
},
39+
})
40+
"""
41+
2742
operation_script = """
2843
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
2944
@@ -33,6 +48,15 @@ def operate(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse):
3348
rsp.output["message"] = "Operation completed successfully"
3449
"""
3550

51+
async_operation_script = """
52+
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
53+
54+
async def operate(req: fnv1.RunFunctionRequest, rsp: fnv1.RunFunctionResponse):
55+
# Set output for operation monitoring
56+
rsp.output["result"] = "success"
57+
rsp.output["message"] = "Operation completed successfully"
58+
"""
59+
3660
both_functions_script = """
3761
from crossplane.function.proto.v1 import run_function_pb2 as fnv1
3862
@@ -91,6 +115,31 @@ class TestCase:
91115
context=structpb.Struct(),
92116
),
93117
),
118+
TestCase(
119+
reason="Function should run async composition scripts with await.",
120+
req=fnv1.RunFunctionRequest(
121+
input=resource.dict_to_struct({"script": async_composition_script}),
122+
),
123+
want=fnv1.RunFunctionResponse(
124+
meta=fnv1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
125+
desired=fnv1.State(
126+
resources={
127+
"bucket": fnv1.Resource(
128+
resource=resource.dict_to_struct(
129+
{
130+
"apiVersion": "s3.aws.upbound.io/v1beta2",
131+
"kind": "Bucket",
132+
"spec": {
133+
"forProvider": {"region": "us-east-1"}
134+
},
135+
}
136+
)
137+
)
138+
}
139+
),
140+
context=structpb.Struct(),
141+
),
142+
),
94143
]
95144

96145
runner = fn.FunctionRunner()
@@ -128,6 +177,23 @@ class TestCase:
128177
),
129178
),
130179
),
180+
TestCase(
181+
reason="Function should run async operation scripts with await.",
182+
req=fnv1.RunFunctionRequest(
183+
input=resource.dict_to_struct({"script": async_operation_script}),
184+
),
185+
want=fnv1.RunFunctionResponse(
186+
meta=fnv1.ResponseMeta(ttl=durationpb.Duration(seconds=60)),
187+
desired=fnv1.State(),
188+
context=structpb.Struct(),
189+
output=resource.dict_to_struct(
190+
{
191+
"result": "success",
192+
"message": "Operation completed successfully",
193+
}
194+
),
195+
),
196+
),
131197
]
132198

133199
runner = fn.FunctionRunner()

0 commit comments

Comments
 (0)