feat(python): optimize FastAPI deployment with direct uvicorn entrypoint#422
feat(python): optimize FastAPI deployment with direct uvicorn entrypoint#422
Conversation
- Replace launcher.py Procfile entrypoint with direct uvicorn invocation following Google Cloud Buildpacks recommended pattern for Python 3.13+ - Add main.py shim at buildpack root to enable `uvicorn main:app` module path - Run 4 workers for parallel request handling on Cloud Run instances - Fix two bugs in lifespan shutdown: settings.use_postgres was referenced as a method object (always truthy) instead of being called, and db_manager was captured as None at import time so the connection pool was never closed; fix by calling settings.use_postgres() and reading dependencies.db_manager through the module at shutdown time - Add httptools for C-level HTTP/1.1 parsing (replaces pure-Python h11); added to both pyproject.toml and requirements.txt since the buildpack uses requirements.txt for dependency installation - Fix pre-commit hook to pass --force-exclude to ruff so lint.exclude in pyproject.toml is respected when files are explicitly specified Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR optimizes the FastAPI deployment for Google Cloud Buildpacks by replacing the Python launcher script with a direct uvicorn command in the Procfile, adding performance improvements via httptools, and fixing critical bugs in the application lifespan management.
Changes:
- Replaced
launcher.pywith directuvicorn main:appcommand in Procfile and added a root-levelmain.pyshim for buildpack compatibility - Fixed two critical bugs in the lifespan shutdown:
settings.use_postgreswas missing()call, anddb_managerwas captured asNoneat import time - Added httptools dependency for C-level HTTP parsing performance
- Fixed pre-commit hook to pass
--force-excludeto ruff to honor exclusion configuration
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/python/src/openapi_server/main.py | Fixed lifespan shutdown bugs: corrected use_postgres() method call and changed to module-level dependencies.db_manager access |
| src/python/main.py | Added entrypoint shim to expose app at main:app path expected by buildpacks |
| src/python/Procfile | Replaced launcher.py with direct uvicorn command using hardcoded port 8080 and 4 workers |
| src/python/pyproject.toml | Added httptools dependency with >=0.5.0 constraint |
| src/python/requirements.txt | Added httptools 0.7.1 for buildpack installation |
| src/python/poetry.lock | Updated lock file with httptools 0.7.1 and updated content hash |
| scripts/hooks/python.sh | Added --force-exclude flag to ruff commands to honor pyproject.toml exclusions |
| @@ -1 +1 @@ | |||
| web: python launcher.py No newline at end of file | |||
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 No newline at end of file | |||
There was a problem hiding this comment.
The hardcoded --port 8080 removes support for the PORT environment variable that was previously respected by launcher.py. Google Cloud Run and other platforms commonly use the PORT environment variable to dynamically assign ports. Consider using --port $PORT or ${PORT:-8080} to maintain backward compatibility while providing a sensible default. Uvicorn also respects the PORT environment variable automatically if --port is not specified, so you could remove the explicit port flag entirely.
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 | |
| web: uvicorn main:app --host 0.0.0.0 --workers 4 |
| fastapi = "^0.115.12" | ||
| pydantic = "^2.12.0" | ||
| uvicorn = "^0.27.1" | ||
| httptools = ">=0.5.0" |
There was a problem hiding this comment.
The version constraint >=0.5.0 is inconsistent with the locked version 0.7.1 in poetry.lock. Poetry's caret (^) constraints are more conventional for non-zero major versions and better express semantic versioning expectations. Consider using ^0.7.0 or ^0.5.0 instead of >=0.5.0 to prevent accidentally pulling in breaking changes from hypothetical future major versions while still allowing minor and patch updates.
| httptools = ">=0.5.0" | |
| httptools = "^0.7.0" |
| @@ -1 +1 @@ | |||
| web: python launcher.py No newline at end of file | |||
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 No newline at end of file | |||
There was a problem hiding this comment.
The hardcoded --workers 4 removes support for the WORKERS environment variable that was previously respected by launcher.py. Different deployment environments may need different worker counts based on available resources. Consider using --workers ${WORKERS:-4} to maintain backward compatibility while providing a sensible default, or allow the platform to configure this through environment variables at deployment time.
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 | |
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers ${WORKERS:-4} |
| @@ -1 +1 @@ | |||
| web: python launcher.py No newline at end of file | |||
| web: uvicorn main:app --host 0.0.0.0 --port 8080 --workers 4 No newline at end of file | |||
There was a problem hiding this comment.
The new Procfile hardcodes port 8080 and 4 workers, but the README.md still documents that PORT and WORKERS environment variables are supported via the launcher script (lines 172-173). Since the Procfile now uses direct uvicorn invocation instead of launcher.py, the documentation needs to be updated to reflect the current behavior. Either update the README to document the new hardcoded values, or modify the Procfile to continue supporting these environment variables.
Summary
launcher.pyin the Procfile withuvicorn main:app --host 0.0.0.0 --port 8080 --workers 4, following the Google Cloud Buildpacks recommended pattern for Python 3.13+; adds amain.pyshim at the buildpack root to expose the app at the expected module pathsettings.use_postgreswas a method reference (always truthy, missing());db_managerwas captured asNoneat import time so the engine pool was never closed — fixed by readingdependencies.db_managerthrough the module at shutdownh11; added to bothpyproject.tomlandrequirements.txtsince the buildpack usesrequirements.txtfor dependency installation--force-excludeto ruff solint.excludeinpyproject.tomlis honoured when files are passed explicitly (was inadvertently linting generated/excluded files)Test plan
uvicorn main:app --port 8080starts andGET /healthreturns{"status":"ok","storage":"memory"}Procfileentrypoint and starts with 4 workers🤖 Generated with Claude Code