Skip to content

Commit 0c2a1af

Browse files
Add Review Helper API service (#5684)
1 parent 0f3462b commit 0c2a1af

31 files changed

Lines changed: 5205 additions & 0 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.DS_Store
2+
__pycache__/
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Database (Cloud SQL)
2+
CLOUD_SQL_INSTANCE=project:region:db-instance
3+
DB_USER=postgres
4+
DB_PASS=password
5+
DB_NAME=database_name
6+
7+
# API Authentication
8+
EXTERNAL_API_KEY=external-api-key
9+
INTERNAL_API_KEY=internal-api-key
10+
11+
# Cloud Tasks
12+
CLOUD_TASKS_PROJECT=gcp-project
13+
CLOUD_TASKS_LOCATION=region
14+
CLOUD_TASKS_QUEUE=queue-name
15+
16+
# Worker URL
17+
WORKER_URL=https://localhost:8080
18+
19+
# Phabricator
20+
PHABRICATOR_URL=https://phabricator.services.mozilla.com
21+
PHABRICATOR_API_KEY=api-key
22+
23+
# Bugzilla
24+
BUGZILLA_URL=https://bugzilla.mozilla.org
25+
BUGZILLA_API_KEY=api-key
26+
27+
# LLM providers
28+
ANTHROPIC_API_KEY=anthropic-api-key
29+
OPENAI_API_KEY=openai-api-key
30+
31+
# Qdrant
32+
QDRANT_API_KEY=qdrant-api-key
33+
QDRANT_LOCATION=http://localhost:6333
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!include:.gitignore
2+
#!include:.dockerignore
3+
4+
.gitignore
5+
.vscode
6+
.venv
7+
*.md
8+
.gcloudignore
9+
.dockerignore
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Python
2+
__pycache__/
3+
*.py[cod]
4+
*$py.class
5+
*.so
6+
.Python
7+
build/
8+
develop-eggs/
9+
dist/
10+
downloads/
11+
eggs/
12+
.eggs/
13+
lib/
14+
lib64/
15+
parts/
16+
sdist/
17+
var/
18+
wheels/
19+
*.egg-info/
20+
.installed.cfg
21+
*.egg
22+
23+
# Virtual environment
24+
.venv/
25+
venv/
26+
ENV/
27+
28+
# Environment variables
29+
.env
30+
31+
# IDE
32+
.idea/
33+
.vscode/
34+
*.swp
35+
*.swo
36+
37+
# Testing
38+
.coverage
39+
htmlcov/
40+
.pytest_cache/
41+
42+
# Local development
43+
*.db
44+
*.sqlite3
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Build stage
2+
FROM python:3.12 AS builder
3+
4+
# Install uv
5+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
6+
7+
WORKDIR /app
8+
9+
# Copy dependency files
10+
COPY pyproject.toml uv.lock ./
11+
12+
# Install dependencies into .venv
13+
RUN uv sync --locked --no-dev
14+
15+
# Runtime stage
16+
FROM python:3.12-slim
17+
18+
WORKDIR /app
19+
20+
COPY --from=builder /app/.venv /app/.venv
21+
22+
# Copy application code
23+
COPY app/ ./app/
24+
COPY alembic/ ./alembic/
25+
COPY alembic.ini ./
26+
27+
# Enable unbuffered mode will ensure real-time logging
28+
ENV PYTHONUNBUFFERED=1
29+
ENV PORT=8080
30+
ENV PATH="/app/.venv/bin:$PATH"
31+
32+
EXPOSE 8080
33+
34+
# Run the server
35+
CMD uvicorn app.main:app --host 0.0.0.0 --port $PORT
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Review Helper Backend
2+
3+
FastAPI backend for Mozilla's Review Helper supporting both Phabricator and GitHub platforms.
4+
5+
## Setup
6+
7+
1. Install dependencies:
8+
9+
```bash
10+
uv sync
11+
```
12+
13+
2. Copy environment variables:
14+
15+
```bash
16+
cp .env.example .env
17+
```
18+
19+
3. Run database migrations:
20+
21+
```bash
22+
uv run alembic upgrade head
23+
```
24+
25+
4. Start the server:
26+
```bash
27+
uv run uvicorn app.main:app --reload
28+
```
29+
30+
## API Documentation
31+
32+
Once the server is running, visit `/docs` for interactive API documentation.
33+
34+
## Cloud SQL Setup
35+
36+
This application supports Google Cloud SQL for PostgreSQL with two connection methods:
37+
38+
### Local Development with Cloud SQL Auth Proxy
39+
40+
1. Install and run the Cloud SQL Auth Proxy:
41+
42+
Follow instructions from the [Cloud SQL Auth Proxy documentation](https://docs.cloud.google.com/sql/docs/postgres/connect-auth-proxy#install).
43+
44+
2. Configure your `.env`:
45+
46+
```bash
47+
DATABASE_URL=postgresql+asyncpg://user:password@127.0.0.1:5432/reviewhelper
48+
```
49+
50+
### Cloud Run Deployment
51+
52+
Cloud Run connects to Cloud SQL via Unix socket automatically. Set these environment variables:
53+
54+
```bash
55+
CLOUD_SQL_INSTANCE=project-id:us-central1:instance-name
56+
DB_USER=reviewhelper
57+
DB_PASS=your-db-password
58+
DB_NAME=reviewhelper
59+
```
60+
61+
Ensure your Cloud Run service has the Cloud SQL connection configured in the deployment settings.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts.
5+
# this is typically a path given in POSIX (e.g. forward slashes)
6+
# format, relative to the token %(here)s which refers to the location of this
7+
# ini file
8+
script_location = %(here)s/alembic
9+
10+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11+
# Uncomment the line below if you want the files to be prepended with date and time
12+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13+
# for all available tokens
14+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15+
# Or organize into date-based subdirectories (requires recursive_version_locations = true)
16+
# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s
17+
18+
# sys.path path, will be prepended to sys.path if present.
19+
# defaults to the current working directory. for multiple paths, the path separator
20+
# is defined by "path_separator" below.
21+
prepend_sys_path = .
22+
23+
# timezone to use when rendering the date within the migration file
24+
# as well as the filename.
25+
# If specified, requires the tzdata library which can be installed by adding
26+
# `alembic[tz]` to the pip requirements.
27+
# string value is passed to ZoneInfo()
28+
# leave blank for localtime
29+
# timezone =
30+
31+
# max length of characters to apply to the "slug" field
32+
# truncate_slug_length = 40
33+
34+
# set to 'true' to run the environment during
35+
# the 'revision' command, regardless of autogenerate
36+
# revision_environment = false
37+
38+
# set to 'true' to allow .pyc and .pyo files without
39+
# a source .py file to be detected as revisions in the
40+
# versions/ directory
41+
# sourceless = false
42+
43+
# version location specification; This defaults
44+
# to <script_location>/versions. When using multiple version
45+
# directories, initial revisions must be specified with --version-path.
46+
# The path separator used here should be the separator specified by "path_separator"
47+
# below.
48+
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
49+
50+
# path_separator; This indicates what character is used to split lists of file
51+
# paths, including version_locations and prepend_sys_path within configparser
52+
# files such as alembic.ini.
53+
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
54+
# to provide os-dependent path splitting.
55+
#
56+
# Note that in order to support legacy alembic.ini files, this default does NOT
57+
# take place if path_separator is not present in alembic.ini. If this
58+
# option is omitted entirely, fallback logic is as follows:
59+
#
60+
# 1. Parsing of the version_locations option falls back to using the legacy
61+
# "version_path_separator" key, which if absent then falls back to the legacy
62+
# behavior of splitting on spaces and/or commas.
63+
# 2. Parsing of the prepend_sys_path option falls back to the legacy
64+
# behavior of splitting on spaces, commas, or colons.
65+
#
66+
# Valid values for path_separator are:
67+
#
68+
# path_separator = :
69+
# path_separator = ;
70+
# path_separator = space
71+
# path_separator = newline
72+
#
73+
# Use os.pathsep. Default configuration used for new projects.
74+
path_separator = os
75+
76+
77+
# set to 'true' to search source files recursively
78+
# in each "version_locations" directory
79+
# new in Alembic version 1.10
80+
# recursive_version_locations = false
81+
82+
# the output encoding used when revision files
83+
# are written from script.py.mako
84+
# output_encoding = utf-8
85+
86+
# database URL. This is consumed by the user-maintained env.py script only.
87+
# other means of configuring database URLs may be customized within the env.py
88+
# file.
89+
# sqlalchemy.url is configured in env.py from app.config
90+
91+
92+
[post_write_hooks]
93+
# post_write_hooks defines scripts or Python functions that are run
94+
# on newly generated revision scripts. See the documentation for further
95+
# detail and examples
96+
97+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
98+
# hooks = black
99+
# black.type = console_scripts
100+
# black.entrypoint = black
101+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
102+
103+
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
104+
# hooks = ruff
105+
# ruff.type = module
106+
# ruff.module = ruff
107+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
108+
109+
# Alternatively, use the exec runner to execute a binary found on your PATH
110+
# hooks = ruff
111+
# ruff.type = exec
112+
# ruff.executable = ruff
113+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
114+
115+
# Logging configuration. This is also consumed by the user-maintained
116+
# env.py script only.
117+
[loggers]
118+
keys = root,sqlalchemy,alembic
119+
120+
[handlers]
121+
keys = console
122+
123+
[formatters]
124+
keys = generic
125+
126+
[logger_root]
127+
level = WARNING
128+
handlers = console
129+
qualname =
130+
131+
[logger_sqlalchemy]
132+
level = WARNING
133+
handlers =
134+
qualname = sqlalchemy.engine
135+
136+
[logger_alembic]
137+
level = INFO
138+
handlers =
139+
qualname = alembic
140+
141+
[handler_console]
142+
class = StreamHandler
143+
args = (sys.stderr,)
144+
level = NOTSET
145+
formatter = generic
146+
147+
[formatter_generic]
148+
format = %(levelname)-5.5s [%(name)s] %(message)s
149+
datefmt = %H:%M:%S
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration with an async dbapi.

0 commit comments

Comments
 (0)