Skip to content

feat: fastapi runtime - #1877

Draft
hallvictoria wants to merge 18 commits into
devfrom
hallvictoria/fastapi-runtime
Draft

feat: fastapi runtime#1877
hallvictoria wants to merge 18 commits into
devfrom
hallvictoria/fastapi-runtime

Conversation

@hallvictoria

Copy link
Copy Markdown
Contributor

Description

Fixes #


Pull Request Checklist

Host-Worker Contract

  • Does this PR impact the host-worker contract (e.g., gRPC messages, shared interfaces)?
    • If yes, have the changes been applied to:
      • azure_functions_worker (Python <= 3.12)
      • proxy_worker (Python >= 3.13)
    • If no, please explain why:

Worker Execution Logic

  • Does this PR affect worker execution logic (e.g., function invocation, bindings, lifecycle)?
    If yes, please answer the following:

Python Version Coverage

  • Does this change apply to both Python <=3.12 and 3.13+?
  • If yes, have the changes been made to:
    • azure_functions_worker (Python <= 3.12)
    • runtimes/v1 / runtimes/v2 (Python >= 3.13)
  • If no, please explain why:

Programming Model Compatibility (for Python 3.13+)

  • Does this change apply to both:
    • V1 programming model (runtimes/v1)?
    • V2 programming model (runtimes/v2)?
  • Explanation (if limited to one model):

@hallvictoria hallvictoria changed the title [feat] fastapi runtime feat: fastapi runtime Aug 12, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_fastapi runtime 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 forces host_stdout to None, which prevents capturing host output in normal runs and makes PYAZURE_WEBHOST_DEBUG ineffective.
    workers/tests/utils/testutils.py:1067
  • if 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_lines entry if __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.

Comment thread workers/tests/utils/testutils.py Outdated
Comment thread runtimes/fastapi/azure_functions_fastapi/handle_event.py
Comment thread runtimes/fastapi/azure_functions_fastapi/runtime.py
Comment thread runtimes/fastapi/pytest.ini

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() adds os.getcwd() to sys.path, but the module import later is based on function_path/function_dir. If the worker process CWD isn’t the function app directory, importing function_app.py/app.py by stem name will fail. Use function_dir (or Path(function_path).parent) when prepending to sys.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 e can 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). Use None as 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)
    

Comment thread runtimes/fastapi/azure_functions_fastapi/handle_event.py Outdated
Comment thread runtimes/fastapi/azure_functions_fastapi/http_v2.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_id is currently set to function_name for 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 in FastAPIConverter.functions, and the host contract expects function_id to 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.toml is set to victorias-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 exact re.match('^...$') against url_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}")

Comment thread runtimes/fastapi/azure_functions_fastapi/loader.py Outdated

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

  • authLevel is 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.methods is a set, so list(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-test looks 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.path at 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 on sys.path rather 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() raises KeyError if 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 with pytest.ini already 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_app is 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:14
  • FastAPIIndexer is 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_dir to sys.path but it already chdirs into that directory (which is typically on sys.path as ""). Mutating sys.path inside tests can leak across the test session; remove this or use monkeypatch.syspath_prepend so it’s automatically reverted.
    runtimes/fastapi/tests/test_example_app.py:67
  • As above, mutating sys.path inside the test can leak across the test session. Since the test already chdirs into test_dir, it should be importable without modifying sys.path (or use monkeypatch.syspath_prepend to 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}")

Comment on lines +4 to +5
import logging.handlers
import traceback
Comment on lines +155 to +159
@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))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants