Skip to content

Commit e56e875

Browse files
committed
fix: propagate context in ce fns.
1 parent 91550eb commit e56e875

File tree

3 files changed

+42
-11
lines changed

3 files changed

+42
-11
lines changed

src/functions_framework/aio/__init__.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
try:
4040
from starlette.applications import Starlette
4141
from starlette.exceptions import HTTPException
42+
from starlette.middleware import Middleware
4243
from starlette.requests import Request
4344
from starlette.responses import JSONResponse, Response
4445
from starlette.routing import Route
@@ -150,9 +151,9 @@ async def handler(request):
150151
await function(event)
151152
else:
152153
# TODO: Use asyncio.to_thread when we drop Python 3.8 support
153-
# Python 3.8 compatible version of asyncio.to_thread
154154
loop = asyncio.get_event_loop()
155-
await loop.run_in_executor(None, function, event)
155+
ctx = contextvars.copy_context()
156+
await loop.run_in_executor(None, ctx.run, function, event)
156157
return Response("OK")
157158

158159
return handler
@@ -295,8 +296,6 @@ def create_asgi_app(target=None, source=None, signature_type=None):
295296
f"Unsupported signature type for ASGI server: {signature_type}"
296297
)
297298

298-
from starlette.middleware import Middleware
299-
300299
app = Starlette(
301300
debug=False,
302301
routes=routes,

tests/test_execution_id_async.py

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -295,23 +295,47 @@ async def test_maintains_execution_id_for_concurrent_requests(monkeypatch, capsy
295295

296296

297297
def test_async_decorator_with_sync_function():
298-
"""Test that the async decorator handles sync functions properly."""
299-
300-
# Create a sync function
301298
def sync_func(request):
302299
return {"status": "ok"}
303300

304-
# Apply the decorator
305301
wrapped = execution_id.set_execution_context_async(enable_id_logging=False)(
306302
sync_func
307303
)
308304

309-
# Create mock request
310305
request = Mock()
311306
request.headers = Mock()
312307
request.headers.get = Mock(return_value="")
313308

314-
# Call the wrapped function - it should be sync since the original was sync
315309
result = wrapped(request)
316310

317311
assert result == {"status": "ok"}
312+
313+
314+
def test_sync_cloudevent_function_has_execution_context(monkeypatch, capsys):
315+
"""Test that sync CloudEvent functions can access execution context."""
316+
monkeypatch.setenv("LOG_EXECUTION_ID", "true")
317+
318+
source = TEST_FUNCTIONS_DIR / "execution_id" / "async_main.py"
319+
target = "sync_cloudevent_with_context"
320+
app = create_asgi_app(target, source, signature_type="cloudevent")
321+
client = TestClient(app)
322+
323+
response = client.post(
324+
"/",
325+
headers={
326+
"ce-specversion": "1.0",
327+
"ce-type": "com.example.test",
328+
"ce-source": "test-source",
329+
"ce-id": "test-id",
330+
"Function-Execution-Id": TEST_EXECUTION_ID,
331+
"Content-Type": "application/json",
332+
},
333+
json={"message": "test"},
334+
)
335+
336+
assert response.status_code == 200
337+
assert response.text == "OK"
338+
339+
record = capsys.readouterr()
340+
assert f"Execution ID in sync CloudEvent: {TEST_EXECUTION_ID}" in record.err
341+
assert "No execution context in sync CloudEvent function!" not in record.err

tests/test_functions/execution_id/async_main.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ async def async_sleep(request):
3636

3737

3838
async def async_trace_test(request):
39-
# Get current execution context
4039
context = execution_id._get_current_context()
4140
return {
4241
"execution_id": context.execution_id if context else None,
@@ -51,3 +50,12 @@ def sync_function_in_async_context(request):
5150
"execution_id": request.headers.get("Function-Execution-Id"),
5251
"type": "sync",
5352
}
53+
54+
55+
def sync_cloudevent_with_context(cloud_event):
56+
"""A sync CloudEvent function that accesses execution context."""
57+
context = execution_id._get_current_context()
58+
if context:
59+
logger.info(f"Execution ID in sync CloudEvent: {context.execution_id}")
60+
else:
61+
logger.error("No execution context in sync CloudEvent function!")

0 commit comments

Comments
 (0)