Skip to content

Commit 23a2427

Browse files
committed
feat: add Docker support with Dockerfile and .dockerignore
1 parent 7d1abfe commit 23a2427

21 files changed

Lines changed: 440 additions & 168 deletions

File tree

.github/workflows/python-ci.yml

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
name: Python CI
2+
3+
on:
4+
[push, pull_request]
5+
6+
permissions:
7+
contents: read
8+
9+
jobs:
10+
11+
test:
12+
name: Lint & Tests
13+
runs-on: ubuntu-latest
14+
timeout-minutes: 10
15+
16+
strategy:
17+
fail-fast: true
18+
19+
steps:
20+
- uses: actions/checkout@v4
21+
22+
- name: Set up Python
23+
uses: actions/setup-python@v5
24+
with:
25+
python-version: "3.13"
26+
27+
- name: Cache pip
28+
uses: actions/cache@v4
29+
with:
30+
path: ~/.cache/pip
31+
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
32+
restore-keys: |
33+
${{ runner.os }}-pip-
34+
35+
- name: Install dependencies
36+
run: |
37+
python -m pip install --upgrade pip
38+
pip install -r requirements.txt
39+
pip install pytest ruff
40+
41+
- name: Lint
42+
run: ruff check .
43+
44+
- name: Run tests
45+
run: pytest
46+
47+
- name: Run Snyk
48+
uses: snyk/actions/python@master
49+
env:
50+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
51+
with:
52+
command: test
53+
54+
55+
docker:
56+
name: Build & Push Docker
57+
needs: test
58+
runs-on: ubuntu-latest
59+
60+
steps:
61+
- uses: actions/checkout@v4
62+
63+
- name: Set version (CalVer)
64+
id: version
65+
run: |
66+
echo "VERSION=$(date +'%Y.%m')" >> $GITHUB_OUTPUT
67+
68+
- name: Login to Docker Hub
69+
uses: docker/login-action@v3
70+
with:
71+
username: ${{ secrets.DOCKERHUB_USERNAME }}
72+
password: ${{ secrets.DOCKERHUB_TOKEN }}
73+
74+
- name: Build and push
75+
uses: docker/build-push-action@v5
76+
with:
77+
push: true
78+
tags: |
79+
${{ secrets.DOCKERHUB_USERNAME }}/app_python:${{ steps.version.outputs.VERSION }}
80+
${{ secrets.DOCKERHUB_USERNAME }}/app_python:latest

app_python/app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
from fastapi import FastAPI
33
from fastapi.middleware.cors import CORSMiddleware
44
from config import DEBUG, PORT, HOST
5-
from health_check.router import router
5+
from routes import health_router, root_router
66
from logger_config import setup_logger
77

88
setup_logger()
99
app = FastAPI(debug=DEBUG)
10-
app.include_router(router=router)
10+
for router in [health_router, root_router]:
11+
app.include_router(router=router)
1112

1213
app.add_middleware(
1314
CORSMiddleware,

app_python/docs/LAB01.md

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
I chose FastApi because it's simple, easy to create endpoints, and has automatic documentation.
44

55
| Framework | Pros | Cons | Reason Not Chosen |
6-
|-------------| ----------------------------------------------------- | ------------------------------------------- | --------------------------------- |
6+
|-------------|-------------------------------------------------------|---------------------------------------------|-----------------------------------|
77
| **FastAPI** | Async support, type safety, OpenAPI, high performance | Slight learning curve | **Chosen** |
88
| Flask | Simple, minimal | No async by default, no built-in validation | Less suitable for structured APIs |
99
| Django | Full-featured, mature | Heavy, overkill for small service | Too complex for this task |
@@ -12,46 +12,45 @@ I chose FastApi because it's simple, easy to create endpoints, and has automatic
1212

1313
1. Environment-based Configuration
1414

15-
```python
16-
HOST = os.getenv("HOST", "0.0.0.0")
17-
PORT = int(os.getenv("PORT", 5000))
18-
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
19-
```
20-
21-
it important because it enables configuration without code changes.
15+
```text
16+
HOST = os.getenv("HOST", "0.0.0.0")
17+
PORT = int(os.getenv("PORT", 5000))
18+
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
19+
```
20+
21+
it important because it enables configuration without code changes.
2222

2323
2. Separation of Concerns
2424

25-
```python
26-
class HealthCheckService:
27-
async def get_info(self, request: Request) -> InfoResponse:
28-
...
29-
30-
```
31-
32-
it important because it easier testing, cleaner routing layer
25+
```text
26+
class HealthCheckService:
27+
async def get_info(self, request: Request) -> InfoResponse:
28+
pass
29+
```
30+
31+
it important because it easier testing, cleaner routing layer
3332

3433
3. Typed Responses with Pydantic
3534

36-
```python
37-
class InfoResponse(BaseModel):
38-
service: ServiceInfo
39-
system: SystemInfo
40-
runtime: RuntimeInfo
41-
request: RequestInfo
42-
endpoints: list[EndpointInfo]
43-
```
44-
45-
it important because guarantees response structure and improves readability
35+
```text
36+
class InfoResponse(BaseModel):
37+
service: ServiceInfo
38+
system: SystemInfo
39+
runtime: RuntimeInfo
40+
request: RequestInfo
41+
endpoints: list[EndpointInfo]
42+
```
43+
44+
it important because guarantees response structure and improves readability
4645

4746
4. Logging
4847

49-
```python
50-
logger = logging.getLogger(__name__)
51-
logger.info("Handling info request")
52-
```
53-
54-
it important because it centralized observability and works seamlessly with Uvicorn
48+
```text
49+
logger = logging.getLogger(__name__)
50+
logger.info("Handling info request")
51+
```
52+
53+
it important because it centralized observability and works seamlessly with Uvicorn
5554

5655
## API Documentation
5756

@@ -111,21 +110,21 @@ I chose FastApi because it's simple, easy to create endpoints, and has automatic
111110
"uptime_seconds": 7390
112111
}
113112
```
114-
113+
115114
3. Testing Commands
116115

117116
Using curl:
118117
```bash
119118
curl http://localhost:5000/
120119
curl http://localhost:5000/health
121120
```
122-
121+
123122
or auto generated documentation:
124-
123+
125124
```bash
126125
http://localhost:5000/docs
127126
```
128-
127+
129128
## Testing Evidence
130129

131130
- Successful responses from `/` and `/health`

app_python/health_check/router.py

Lines changed: 0 additions & 18 deletions
This file was deleted.

app_python/health_check/service.py

Lines changed: 0 additions & 104 deletions
This file was deleted.

app_python/requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
uvicorn==0.40.0
22
pydantic==2.12.5
3-
fastapi==0.128.0
3+
fastapi==0.128.0
4+
pytest==9.0.2

app_python/routes/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from .health_check.router import router as health_router
2+
from .root.router import router as root_router
3+
4+
__all__ = ["root_router", "health_router"]
File renamed without changes.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from fastapi import APIRouter
2+
from routes.health_check.schemas import HealthResponse
3+
from routes.health_check.service import HealthCheckServiceDep
4+
5+
router = APIRouter()
6+
7+
@router.get("/health", description="Health check")
8+
async def health_check(service: HealthCheckServiceDep) -> HealthResponse:
9+
return await service.health_check()
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from pydantic import BaseModel
2+
from datetime import datetime
3+
4+
class HealthResponse(BaseModel):
5+
status: str
6+
timestamp: datetime
7+
uptime_seconds: int

0 commit comments

Comments
 (0)