Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
88 changes: 76 additions & 12 deletions RUNNING.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,97 @@
# Running the Todo API
# Running the Hello API

## TEAM_BRIEF
stack: Python/FastAPI
test_runner: pytest tests/
lint_tool: ruff check .
coverage_tool: pytest-cov
coverage_threshold: 70
coverage_applies: true

## Prerequisites

- Python 3.10 or later
- Docker and Docker Compose installed, **or**
- Python 3.11+ installed locally

---

## Docker-based Setup (Recommended)

### Build and Start the Server

```bash
docker-compose up --build
```

The API will be available at `http://localhost:8000`.

### Verify the /hello Endpoint

```bash
curl http://localhost:8000/hello
```

Expected response:

## Install dependencies
```json
{"message": "hello"}
```

### Run Tests inside Docker

```bash
pip install fastapi uvicorn pydantic
docker-compose run --rm api pytest tests/ -v
```

For running the test suite you will also need:
### Stop the Server

```bash
pip install httpx pytest
docker-compose down
```

## Start the server
---

## Local Setup (without Docker)

### Install Dependencies

```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
pip install -r requirements.txt
```

The API will be available at <http://localhost:8000>.
### Start the Server

Interactive docs are served at <http://localhost:8000/docs>.
```bash
uvicorn app.main:app --host 0.0.0.0 --port 8000
```

## Run the tests
### Run Tests

```bash
pytest tests/
pytest tests/ -v
```

---

## Project Structure

```
.
├── app/
│ ├── __init__.py
│ └── main.py # FastAPI application with GET /hello
├── tests/
│ ├── __init__.py
│ └── test_hello.py # Pytest tests for the /hello endpoint
├── conftest.py # Root pytest configuration
├── requirements.txt # Python dependencies
├── Dockerfile # Container image definition
├── docker-compose.yml # Docker Compose orchestration
└── RUNNING.md # This file
```

## API Endpoints

| Method | Path | Description | Response |
|--------|----------|-----------------------------------|-----------------------|
| GET | `/hello` | Returns a JSON greeting message | `{"message": "hello"}` |
Empty file added app/__init__.py
Empty file.
20 changes: 20 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""FastAPI application entry point.

Initialises the FastAPI app and defines the GET /hello endpoint.
"""

from __future__ import annotations

from fastapi import FastAPI

app = FastAPI(
title="Hello API",
description="A minimal API server with a /hello endpoint.",
version="0.1.0",
)


@app.get("/hello", tags=["hello"])
async def hello() -> dict:
"""Return a simple JSON greeting."""
return {"message": "hello"}
5 changes: 5 additions & 0 deletions conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
import sys
from pathlib import Path

# Ensure the project root is on sys.path so that 'app' package is importable.
_PROJECT_ROOT = str(Path(__file__).resolve().parent)
if _PROJECT_ROOT not in sys.path:
sys.path.insert(0, _PROJECT_ROOT)


def pytest_addoption(parser):
"""Register --timeout so pytest doesn't choke when the plugin is absent."""
Expand Down
12 changes: 12 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3.9"

services:
api:
build: .
ports:
- "8000:8000"
volumes:
- .:/app
environment:
- PYTHONUNBUFFERED=1
command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload
10 changes: 4 additions & 6 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
fastapi>=0.100.0
uvicorn>=0.23.0
pydantic>=2.0.0
pytest>=7.0.0
pytest-timeout>=2.1.0
httpx>=0.24.0
fastapi>=0.110.0,<1.0.0
uvicorn>=0.29.0,<1.0.0
pytest>=8.0.0,<9.0.0
httpx>=0.27.0,<1.0.0
42 changes: 42 additions & 0 deletions tests/test_hello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Tests for the GET /hello endpoint.

Verifies that the endpoint returns HTTP 200 and the expected JSON body.
"""

from __future__ import annotations

from fastapi.testclient import TestClient

from app.main import app

client = TestClient(app)


def test_get_hello_status_code() -> None:
"""GET /hello should return HTTP 200."""
response = client.get("/hello")
assert response.status_code == 200


def test_get_hello_json_body() -> None:
"""GET /hello should return JSON {'message': 'hello'}."""
response = client.get("/hello")
assert response.json() == {"message": "hello"}


def test_get_hello_content_type() -> None:
"""GET /hello should return application/json content type."""
response = client.get("/hello")
assert "application/json" in response.headers["content-type"]


def test_post_hello_not_allowed() -> None:
"""POST /hello should return HTTP 405 Method Not Allowed."""
response = client.post("/hello")
assert response.status_code == 405


def test_nonexistent_endpoint_returns_404() -> None:
"""Requesting a non-existent path should return HTTP 404."""
response = client.get("/nonexistent")
assert response.status_code == 404