feat: fastapi runtime - #1877
Conversation
There was a problem hiding this comment.
Pull request overview
This pull request introduces a new prototype FastAPI runtime under runtimes/fastapi for the Azure Functions Python Worker, including packaging metadata, runtime event handling, route indexing/conversion, and a suite of unit tests/fixtures demonstrating discovery and indexing behavior.
Changes:
- Added the
azure_functions_fastapiruntime implementation (loader/indexer/converter/handler/http_v2 + event handlers) and Python packaging entry point. - Added FastAPI-focused unit tests plus modular app fixtures to validate indexing and script-file discovery behavior.
- Added documentation/design materials for the runtime base approach and the FastAPI runtime prototype.
Reviewed changes
Copilot reviewed 37 out of 38 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| workers/tests/utils/testutils.py | Adjusts test harness webhost logging/stdout handling and log-check gating. |
| runtimes/fastapi/tests/test_modular_app.py | Tests indexing of modular FastAPI apps using routers and ensures unregistered routers aren’t indexed. |
| runtimes/fastapi/tests/test_indexer.py | Unit tests for basic route discovery and async detection behavior. |
| runtimes/fastapi/tests/test_example_app.py | Tests indexing and conversion against a sample FastAPI app. |
| runtimes/fastapi/tests/test_converter.py | Unit tests for conversion of indexed routes into Azure Functions metadata/bindings. |
| runtimes/fastapi/tests/test_app_discovery.py | Tests script-file selection precedence and indexer propagation of selected script file. |
| runtimes/fastapi/tests/fixtures/modular_app/function_app.py | Modular FastAPI fixture entry point registering routers. |
| runtimes/fastapi/tests/fixtures/modular_app/app/schemas.py | Pydantic models for modular fixture. |
| runtimes/fastapi/tests/fixtures/modular_app/app/routers/users.py | Users router fixture with nested router. |
| runtimes/fastapi/tests/fixtures/modular_app/app/routers/unregistered.py | Unregistered router fixture used to validate non-indexing behavior. |
| runtimes/fastapi/tests/fixtures/modular_app/app/routers/root.py | Root router fixture. |
| runtimes/fastapi/tests/fixtures/modular_app/app/routers/items.py | Items router fixture. |
| runtimes/fastapi/tests/example_app.py | Example FastAPI app used by tests. |
| runtimes/fastapi/tests/init.py | Initializes the FastAPI runtime test package. |
| runtimes/fastapi/requirements.txt | Placeholder requirements file pointing at the local package. |
| runtimes/fastapi/README.md | Prototype design/usage documentation for the FastAPI runtime. |
| runtimes/fastapi/pytest.ini | Pytest configuration for the runtime’s test suite (incl. coverage settings). |
| runtimes/fastapi/pyproject.toml | Packaging metadata and entry-point registration for runtime discovery. |
| runtimes/fastapi/azure_functions_fastapi/version.py | Declares the runtime version string. |
| runtimes/fastapi/azure_functions_fastapi/utils/wrappers.py | Exception-wrapping utility for adding contextual error messages. |
| runtimes/fastapi/azure_functions_fastapi/utils/tracing.py | Exception serialization helper for gRPC/proto responses. |
| runtimes/fastapi/azure_functions_fastapi/utils/helpers.py | Worker metadata helper for init responses. |
| runtimes/fastapi/azure_functions_fastapi/utils/executor.py | ContextVar for invocation ID correlation. |
| runtimes/fastapi/azure_functions_fastapi/utils/constants.py | Constants and environment variable names/capabilities for the runtime. |
| runtimes/fastapi/azure_functions_fastapi/utils/app_setting_manager.py | Helper for reading app settings from environment variables. |
| runtimes/fastapi/azure_functions_fastapi/utils/init.py | Initializes runtime utils package. |
| runtimes/fastapi/azure_functions_fastapi/runtime.py | RuntimeBase implementation that delegates worker events to handlers. |
| runtimes/fastapi/azure_functions_fastapi/logging.py | Runtime logging helpers. |
| runtimes/fastapi/azure_functions_fastapi/loader.py | Imports customer app, indexes routes, and builds function metadata. |
| runtimes/fastapi/azure_functions_fastapi/indexer.py | Discovers FastAPI routes and produces internal metadata records. |
| runtimes/fastapi/azure_functions_fastapi/http_v2.py | HTTP v2 streaming server/coordinator implementation. |
| runtimes/fastapi/azure_functions_fastapi/handler.py | Request adaptation + direct endpoint invocation + response formatting. |
| runtimes/fastapi/azure_functions_fastapi/handle_event.py | Worker event handlers for init/metadata/load/invoke/reload. |
| runtimes/fastapi/azure_functions_fastapi/converter.py | Converts indexed routes into Azure Functions binding metadata structures. |
| runtimes/fastapi/azure_functions_fastapi/bindings/init.py | Initializes bindings package. |
| runtimes/fastapi/azure_functions_fastapi/init.py | Package exports and compatibility stubs for the proxy worker. |
| RUNTIME_BASE_DESIGN.md | Design proposal document for runtime-base extensibility. |
| .gitignore | Ignores .github/agents. |
Suppressed comments (3)
workers/tests/utils/testutils.py:231
cls.host_stdout = None if True ...always forceshost_stdoutto None, which prevents capturing host output in normal runs and makes PYAZURE_WEBHOST_DEBUG ineffective.
workers/tests/utils/testutils.py:1067if True:makes the webhost always inherit stdout (noisy in CI) and ignores the PYAZURE_WEBHOST_DEBUG flag documented elsewhere in this module.
runtimes/fastapi/pytest.ini:30- The
exclude_linesentryif __name__ == .__main__.:is malformed (missing quotes) so it won’t match any lines in coverage reporting.
raise NotImplementedError
if __name__ == .__main__.:
if TYPE_CHECKING:
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 38 out of 39 changed files in this pull request and generated 2 comments.
Suppressed comments (4)
Previously missed (3) — in code that hasn't changed since the last review.
runtimes/fastapi/azure_functions_fastapi/loader.py:233
load_function_metadata()addsos.getcwd()tosys.path, but the module import later is based onfunction_path/function_dir. If the worker process CWD isn’t the function app directory, importingfunction_app.py/app.pyby stem name will fail. Usefunction_dir(orPath(function_path).parent) when prepending tosys.path.
# Add current directory to Python path if needed
current_dir = os.getcwd()
if current_dir not in sys.path:
sys.path.insert(0, current_dir)
runtimes/fastapi/azure_functions_fastapi/handler.py:94
- The error response currently returns
str(e)to the client. That can leak internal details (including exception messages that may contain paths/config) and makes it harder to control what’s exposed. Prefer a generic 500 body and log the exception server-side.
return {
'status_code': 500,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps({'error': str(e)})
}
runtimes/fastapi/azure_functions_fastapi/utils/wrappers.py:34
- Re-raising with
raise type(e)(enhanced_message) from ecan drop important exception details/attributes (e.g.,ModuleNotFoundError.name) and can fail for exception types that require additional ctor args. It’s safer to update the exception args and re-raise the same instance.
if debug_logs:
enhanced_message += f"\n{debug_logs}"
# Re-raise with enhanced message
raise type(e)(enhanced_message) from e
runtimes/fastapi/azure_functions_fastapi/handle_event.py:146
if not _metadata_result:treats an empty-but-valid metadata list as “not indexed yet”, causing repeated indexing and ultimately returning a failure for apps that intentionally expose zero routes (or have docs disabled). UseNoneas the sentinel for “not indexed”.
# If we haven't indexed yet, do it now
if not _metadata_result:
_fastapi_app, _metadata_result, _converter = load_function_metadata(
function_path, function_app_directory, protos)
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 39 out of 40 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (3) — in code that hasn't changed since the last review.
runtimes/fastapi/azure_functions_fastapi/indexer.py:85
function_idis currently set tofunction_namefor every route. If a user registers multiple routes that share the same endpoint callable name (or documentation routes collide), the later route will silently overwrite the earlier one inFastAPIConverter.functions, and the host contract expectsfunction_idto be unique (see proto comment: "avoid name collisions"). Consider failing fast on duplicates with a clear error.
# Generate a unique function name from the route
function_name = (
f"fastapi_{route.name}"
if is_documentation_route
else self._generate_function_name(route)
runtimes/fastapi/pyproject.toml:4
- The project distribution name in
pyproject.tomlis set tovictorias-fastapi-test, which looks like a personal/temporary placeholder and is inconsistent with the README’s described package identity. Using a neutral prototype name avoids accidentally publishing/installing a personal-named artifact.
[project]
name = "victorias-fastapi-test"
dynamic = ["version"]
requires-python = ">=3.10"
runtimes/fastapi/azure_functions_fastapi/handler.py:145
_extract_path_params()logs multiple INFO/WARNING lines per request and uses an exactre.match('^...$')againsturl_path. This is noisy in production and also requires hardcoding a particular route prefix stripping strategy to make the match succeed. Switching to DEBUG logging and matching the route regex at the end of the URL path (re.search(... + '$')) makes it work regardless of the configured Functions route prefix, while avoiding excessive logs.
# Debug logging
from .logging import logger
logger.info(f"[FastAPI Handler] Request URL: {request_url}")
logger.info(f"[FastAPI Handler] Extracted path: {url_path}")
logger.info(f"[FastAPI Handler] Route pattern: {route_path}")
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 41 changed files in this pull request and generated 2 comments.
Suppressed comments (14)
Previously missed (11) — in code that hasn't changed since the last review.
runtimes/fastapi/azure_functions_fastapi/loader.py:79
authLevelis emitted as"ANONYMOUS"in raw bindings. In this repo’s function.json files and docs the value is lowercase (e.g.,"anonymous"), and the host expects that casing; uppercasing risks the trigger being rejected or treated as non-anonymous.
if binding['type'] == 'httpTrigger':
raw_binding["authLevel"] = "ANONYMOUS" # Uppercase to match v2 runtime
raw_binding["methods"] = [m.lower() for m in func_info.http_methods]
runtimes/fastapi/azure_functions_fastapi/indexer.py:90
route.methodsis a set, solist(route.methods)produces a non-deterministic order. This can make emitted metadata (and any tests that compare method lists) flaky; sort the methods for stable output.
# Get HTTP methods for this route
http_methods = list(route.methods)
route_path = (
runtimes/fastapi/pyproject.toml:2
- The distribution name
victorias-fastapi-testlooks like a personal/temporary identifier and is likely to be confusing in a shared repo. Use a neutral, descriptive package name consistent with the README’s proposed distribution identity.
name = "victorias-fastapi-test"
runtimes/fastapi/tests/test_example_app.py:12
- This module mutates
sys.pathat import time, which can leak into unrelated tests and make test behavior order-dependent. Prefer relying on editable install (pip install -e .) and/or the current working directory being onsys.pathrather than globally modifying it.
This issue also appears in the following locations of the same file:
- line 25
- line 65
runtimes/fastapi/azure_functions_fastapi/http_v2.py:148
HttpCoordinator.set_http_response()raisesKeyErrorif the context entry doesn’t exist yet.invocation_request()calls this in the exception path, which can happen before any HTTP request is received/registered, causing the error handler itself to fail.
def set_http_response(self, invoc_id, http_response):
if invoc_id not in self._context_references:
raise KeyError("No context reference found for invocation %s" % invoc_id)
context_ref = self._context_references.get(invoc_id)
context_ref.http_response = http_response
runtimes/fastapi/azure_functions_fastapi/handler.py:126
- These per-request logs are emitted at INFO and include request URL/path/pattern details; this will be very noisy in production and can significantly increase log volume/cost. Consider switching these to DEBUG (and avoid f-strings so formatting is skipped when DEBUG is off).
This issue also appears on line 136 of the same file.
from .logging import logger
logger.info(f"[FastAPI Handler] Request URL: {request_url}")
logger.info(f"[FastAPI Handler] Extracted path: {url_path}")
logger.info(f"[FastAPI Handler] Route pattern: {route_path}")
runtimes/fastapi/tests/test_example_app.py:53
- Unconditional
print()statements in unit tests add noise to CI logs (especially withpytest.inialready using-v). If this output is only for debugging, it should be removed or gated behind a flag.
runtimes/fastapi/README.md:51 - The README’s sample code is over-indented inside the endpoint functions (
return ...), which makes the example invalid Python if copied as-is.
@app.get("/hello")
async def hello():
return {"message": "Hello from FastAPI on Azure Functions"}
runtimes/fastapi/tests/test_example_app.py:91
- Unconditional
print()statements in unit tests add noise to CI logs. If this output is only for debugging, remove it or gate it behind a flag.
runtimes/fastapi/tests/test_indexer.py:9 index_fastapi_appis imported but never used in this test module. This will trigger unused-import linting (e.g., flake8 F401) if enabled.
runtimes/fastapi/tests/test_example_app.py:14FastAPIIndexeris imported but never used in this test module. This will trigger unused-import linting (e.g., flake8 F401) if enabled.
runtimes/fastapi/tests/test_example_app.py:28
- The test adds
test_dirtosys.pathbut it alreadychdirs into that directory (which is typically onsys.pathas ""). Mutatingsys.pathinside tests can leak across the test session; remove this or usemonkeypatch.syspath_prependso it’s automatically reverted.
runtimes/fastapi/tests/test_example_app.py:67 - As above, mutating
sys.pathinside the test can leak across the test session. Since the test alreadychdirs intotest_dir, it should be importable without modifyingsys.path(or usemonkeypatch.syspath_prependto ensure cleanup).
runtimes/fastapi/azure_functions_fastapi/handler.py:136 - This INFO log is emitted on every request and includes the generated regex pattern. For typical HTTP traffic this is high-volume diagnostic detail; consider DEBUG level and use logger formatting so the string isn’t built when DEBUG is disabled.
logger.info(f"[FastAPI Handler] Regex pattern: {pattern}")
| import logging.handlers | ||
| import traceback |
| @attach_message_to_exception( | ||
| expt_type=(ImportError, ModuleNotFoundError), | ||
| message="Cannot find module. Please check the requirements.txt file for the " | ||
| "missing module. Current sys.path: " + " ".join(sys.path), | ||
| debug_logs="Error when indexing FastAPI app. Sys Path:" + " ".join(sys.path)) |
Description
Fixes #
Pull Request Checklist
Host-Worker Contract
Worker Execution Logic
If yes, please answer the following:
Python Version Coverage
Programming Model Compatibility (for Python 3.13+)