Skip to content

Commit f7131af

Browse files
committed
feat: Update Dockerfile and main.py with enhanced comments and health check endpoint; add uvicorn to requirements
1 parent c67cf9c commit f7131af

3 files changed

Lines changed: 160 additions & 15 deletions

File tree

backend/Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ENV PYTHONDONTWRITEBYTECODE=1 \
88
PIP_NO_CACHE_DIR=1
99

1010
RUN apt-get update && apt-get install -y --no-install-recommends \
11-
curl git \
11+
curl git bash-completion less procps \
1212
&& rm -rf /var/lib/apt/lists/*
1313

1414
COPY requirements.txt .
@@ -37,4 +37,4 @@ USER gcs
3737
EXPOSE 8000
3838

3939
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", \
40-
"--workers", "2", "--log-level", "info"]
40+
"--workers", "2", "--log-level", "info"]

backend/main.py

Lines changed: 150 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,33 @@
11
"""
22
Ground Control Station — FastAPI application entry point.
3-
4-
This is a minimal scaffold. Students should implement full functionality:
3+
=========================================================
4+
5+
This module is the heart of the backend. FastAPI reads this file when the
6+
server starts and uses the `app` object defined here to handle every incoming
7+
HTTP request.
8+
9+
Key concepts demonstrated in this file
10+
---------------------------------------
11+
* **FastAPI application factory** — how to create and configure an `app`.
12+
* **CORS middleware** — what Cross-Origin Resource Sharing is and why it's
13+
needed when a browser frontend talks to a separate backend server.
14+
* **Environment variables** — the standard way to inject runtime configuration
15+
(URLs, log levels, secrets) without hard-coding values.
16+
* **Structured logging** — using Python's built-in `logging` module rather
17+
than plain `print()` calls, which is the industry standard.
18+
* **Async route handlers** — why `async def` matters for I/O-bound work like
19+
HTTP calls to a robot API.
20+
* **Error handling** — catching specific exceptions and returning meaningful
21+
responses instead of letting the server crash.
22+
23+
Running the server locally
24+
--------------------------
25+
From the `backend/` directory:
26+
27+
uvicorn main:app --host 0.0.0.0 --port 8000 --reload
28+
29+
Then visit http://localhost:8000/docs for the interactive API documentation
30+
that FastAPI generates automatically from your code (no extra work required).
531
"""
632

733
import logging
@@ -12,21 +38,69 @@
1238

1339
from robot_client import robot, RobotConnectionError
1440

41+
# ── Configuration from environment variables ───────────────────────────────
42+
# os.getenv(key, default) reads a value from the process environment.
43+
# If the variable is not set, the second argument is used as a fallback.
44+
#
45+
# Why environment variables instead of hard-coded strings?
46+
# • Different environments (dev / staging / production) can use different
47+
# values without changing source code.
48+
# • Secrets (API keys, passwords) are never committed to version control.
49+
# • The values can be overridden in docker-compose.yml or a .env file
50+
# without touching this file.
1551
ROBOT_API_URL = os.getenv("ROBOT_API_URL", "http://localhost:5000")
1652
LOG_LEVEL = os.getenv("LOG_LEVEL", "info")
1753

54+
# ── Logging setup ──────────────────────────────────────────────────────────
55+
# basicConfig() configures the root logger once at startup.
56+
# LOG_LEVEL.upper() converts e.g. "info" → "INFO" to match the constants
57+
# expected by the logging module (logging.INFO, logging.DEBUG, etc.).
58+
#
59+
# Use `logger.info(...)`, `logger.warning(...)`, `logger.error(...)` etc.
60+
# throughout your code instead of print(). Benefits:
61+
# • Timestamps, severity levels, and module names are added automatically.
62+
# • Output can be redirected to files or log aggregators without code changes.
63+
# • Verbosity is controlled at runtime via LOG_LEVEL, not by editing code.
1864
logging.basicConfig(level=LOG_LEVEL.upper())
1965
logger = logging.getLogger(__name__)
66+
# __name__ is the module's fully-qualified name (e.g. "main").
67+
# Using __name__ means log messages identify which module produced them.
2068

2169

22-
# ── App ────────────────────────────────────────────────────────────────────
23-
70+
# ── Application factory ────────────────────────────────────────────────────
71+
# FastAPI() creates the ASGI application object. Everything—routes,
72+
# middleware, startup hooks—is registered on this object.
73+
#
74+
# The metadata arguments (title, description, version) appear in:
75+
# • The auto-generated /docs (Swagger UI) page.
76+
# • The /openapi.json schema that clients can use to generate SDK code.
2477
app = FastAPI(
2578
title="Ground Control Station",
2679
description="CMP9134 — Robot Management System scaffold",
2780
version="0.1.0",
2881
)
2982

83+
# ── CORS middleware ────────────────────────────────────────────────────────
84+
# Browsers enforce the Same-Origin Policy: a web page served from one origin
85+
# (e.g. http://localhost:3000) is NOT allowed to make fetch() requests to a
86+
# different origin (e.g. http://localhost:8000) unless that second origin
87+
# explicitly opts in via CORS headers.
88+
#
89+
# CORSMiddleware adds the necessary HTTP response headers so the browser
90+
# permits the cross-origin request.
91+
#
92+
# allow_origins=["*"]
93+
# Permit requests from ANY origin. Fine for development or a controlled
94+
# lab network, but in production you should list specific allowed origins
95+
# (e.g. ["https://yourapp.example.com"]) to prevent abuse.
96+
#
97+
# allow_credentials=True
98+
# Allow cookies and HTTP authentication headers to be sent cross-origin.
99+
# Note: this cannot be combined with allow_origins=["*"] in a real
100+
# production deployment — the browser will reject it.
101+
#
102+
# allow_methods=["*"], allow_headers=["*"]
103+
# Don't restrict which HTTP verbs or request headers are allowed.
30104
app.add_middleware(
31105
CORSMiddleware,
32106
allow_origins=["*"],
@@ -37,33 +111,96 @@
37111

38112

39113
# ── Health check ───────────────────────────────────────────────────────────
40-
41-
114+
# A health check endpoint is a simple route that returns 200 OK when the
115+
# server is running correctly. It is called by:
116+
# • Docker — via the HEALTHCHECK instruction in the Dockerfile, which marks
117+
# the container "healthy" once this endpoint responds successfully.
118+
# • The CI pipeline — the compose-test job polls this URL before running
119+
# further tests (see .github/workflows/ci.yml).
120+
# • Load balancers — to decide whether to route traffic to this instance.
121+
#
122+
# include_in_schema=False hides this internal route from the /docs page
123+
# because it is infrastructure plumbing, not part of the public API.
124+
#
125+
# Note: this is a plain `def` (synchronous), not `async def`. That is fine
126+
# here because the function does no I/O — it just returns a dict immediately.
127+
# FastAPI handles both sync and async handlers correctly.
42128
@app.get("/health", include_in_schema=False)
43129
def health():
44130
return {"status": "ok"}
45131

46132

47-
# ── Example: robot status ──────────────────────────────────────────────────
133+
# ── Robot status proxy ─────────────────────────────────────────────────────
48134
# TODO: add authentication, RBAC, and mission logging around these endpoints.
49-
50-
135+
#
136+
# This route is `async def` because it calls `await robot.get_status()`,
137+
# which performs a network request to the robot simulator.
138+
#
139+
# Why async matters for network I/O
140+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
141+
# FastAPI uses an async event loop (from the `asyncio` standard library).
142+
# When an async handler reaches an `await`, it *suspends itself* and lets the
143+
# event loop handle other incoming requests, rather than blocking the whole
144+
# process. This means one Python process can serve many concurrent requests
145+
# without needing a thread per request — crucial for a real-time robotics
146+
# dashboard where many clients may be connected simultaneously.
147+
#
148+
# Error handling pattern
149+
# ~~~~~~~~~~~~~~~~~~~~~~
150+
# We catch `RobotConnectionError` specifically — not bare `Exception`.
151+
# Catching only the errors we expect means unexpected bugs (e.g. a TypeError
152+
# from bad data) still propagate and cause a proper 500 response, rather than
153+
# being silently swallowed by an overly broad except clause.
154+
#
155+
# Returning {"error": str(exc)} gives the frontend a machine-readable message
156+
# instead of letting FastAPI produce an unhelpful 500 HTML page.
51157
@app.get("/api/status")
52158
async def get_status():
53-
"""Proxy the robot status. Replace/extend with your own logic."""
159+
"""Return the current robot status (position, battery level, state).
160+
161+
Proxies the request to the Virtual Robot API via ``robot_client.RobotClient``.
162+
Returns the robot's JSON payload directly, or an error dict if the robot
163+
simulator is unreachable.
164+
"""
54165
try:
55166
return await robot.get_status()
56167
except RobotConnectionError as exc:
168+
# Log the error server-side at WARNING level so it appears in the
169+
# server logs without spamming at ERROR level for expected downtime.
170+
logger.warning("Could not reach robot API: %s", exc)
57171
return {"error": str(exc)}
58172

59173

60174
# ── TODO: add your routes below ────────────────────────────────────────────
61-
# Example route skeletons:
175+
# Use the skeletons below as starting points. Each route should:
176+
# 1. Validate inputs — FastAPI does this automatically when you add type
177+
# hints to the function parameters (e.g. `x: int`).
178+
# 2. Call the appropriate RobotClient method from robot_client.py.
179+
# 3. Handle RobotConnectionError (and any other expected errors) gracefully.
180+
# 4. Return a meaningful JSON response.
62181
#
63182
# @app.post("/api/move")
64183
# async def move(x: int, y: int):
65-
# ...
184+
# """Send the robot to position (x, y)."""
185+
# try:
186+
# return await robot.move(x, y)
187+
# except RobotConnectionError as exc:
188+
# logger.warning("Move command failed: %s", exc)
189+
# return {"error": str(exc)}
66190
#
67191
# @app.websocket("/ws/telemetry")
68192
# async def ws_telemetry(websocket: WebSocket):
69-
# ...
193+
# """Stream live sensor data to a connected browser client.
194+
#
195+
# WebSockets maintain a persistent two-way connection, making them ideal
196+
# for low-latency telemetry feeds (position, battery, sensor readings).
197+
# Unlike HTTP, you don't need to poll — the server pushes updates.
198+
# """
199+
# await websocket.accept()
200+
# try:
201+
# while True:
202+
# data = await robot.get_status()
203+
# await websocket.send_json(data)
204+
# await asyncio.sleep(0.5) # push an update every 500 ms
205+
# except WebSocketDisconnect:
206+
# logger.info("Telemetry client disconnected")

backend/requirements.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
# This file lists the Python dependencies for the backend of your project.
2+
# You can add any additional dependencies your project requires here.
3+
4+
# fastapi is a modern, fast (high-performance) web framework for building APIs with Python 3.6+ based on standard Python type hints. it automatically generates interactive API documentation (at /docs) and provides a simple and intuitive way to define API endpoints.
15
fastapi
6+
7+
# uvicorn is a lightning-fast ASGI server implementation, using uvloop and httptools. it's designed to run fastapi applications in production, providing high performance and low latency. the [standard] extra includes additional dependencies for optimal performance and features.
28
uvicorn[standard]
9+
10+
# httpx is a fully featured HTTP client for Python 3, which provides a simple and intuitive API for making HTTP requests. it supports both synchronous and asynchronous programming styles, making it a great choice for testing your API endpoints or making HTTP requests to other services.
311
httpx

0 commit comments

Comments
 (0)