Skip to content

Commit b082cca

Browse files
committed
Typing enhancements
1 parent bac92f9 commit b082cca

File tree

7 files changed

+39
-44
lines changed

7 files changed

+39
-44
lines changed

.devcontainer/devcontainer.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,21 @@
2323
"ghcr.io/devcontainers/features/python:1": {
2424
"version": "3.13",
2525
"installTools": true
26+
},
27+
"ghcr.io/devcontainers/features/git:1": {
28+
"version": "latest"
2629
}
2730
},
2831

32+
// Forward SSH agent (more secure than mounting keys)
33+
// This forwards your host's SSH agent to the container
34+
"mounts": [
35+
"source=${env:SSH_AUTH_SOCK},target=/ssh-agent,type=bind,consistency=cached"
36+
],
37+
"remoteEnv": {
38+
"SSH_AUTH_SOCK": "/ssh-agent"
39+
},
40+
2941
// Use 'forwardPorts' to make a list of ports inside the container available locally.
3042
"forwardPorts": [8000],
3143

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"python-envs.defaultEnvManager": "ms-python.python:poetry",
3+
"python-envs.defaultPackageManager": "ms-python.python:poetry",
4+
"python-envs.pythonProjects": []
5+
}

app/main.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
"""FastAPI application entry point."""
22

3-
from typing import Dict
4-
53
from fastapi import FastAPI
64

75
from app.routers import todo_lists
86

97
# Create FastAPI application instance
108
app = FastAPI(
119
title="TodoList API",
12-
description="A simple Todo List API for Python/FastAPI candidates",
10+
description="A simple Todo List API",
1311
version="1.0.0",
1412
)
1513

@@ -18,7 +16,7 @@
1816

1917

2018
@app.get("/", tags=["health"])
21-
async def root() -> Dict[str, str]:
19+
async def root() -> dict[str, str]:
2220
"""
2321
Health check endpoint.
2422

app/routers/todo_lists.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""TodoList API router with CRUD endpoints."""
22

3-
from typing import Annotated, List
3+
from typing import Annotated
44

55
from fastapi import APIRouter, Depends, HTTPException, status
66

@@ -10,10 +10,10 @@
1010
router = APIRouter(prefix="/api/todolists", tags=["todolists"])
1111

1212

13-
@router.get("", response_model=List[TodoList], status_code=status.HTTP_200_OK)
13+
@router.get("", response_model=list[TodoList], status_code=status.HTTP_200_OK)
1414
async def index(
1515
service: Annotated[TodoListService, Depends(get_todo_list_service)],
16-
) -> List[TodoList]:
16+
) -> list[TodoList]:
1717
"""
1818
Get all todo lists.
1919

app/services/todo_lists.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""TodoList service with in-memory storage."""
22

3-
from typing import List, Optional
3+
from typing import Optional
44

55
from app.models import TodoList, TodoListCreate, TodoListUpdate
66

@@ -10,10 +10,10 @@ class TodoListService:
1010

1111
def __init__(self) -> None:
1212
"""Initialize the service with empty storage."""
13-
self._storage: List[TodoList] = []
13+
self._storage: list[TodoList] = []
1414
self._next_id: int = 1
1515

16-
def all(self) -> List[TodoList]:
16+
def all(self) -> list[TodoList]:
1717
"""
1818
Get all todo lists.
1919

poetry.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[virtualenvs]
2+
in-project = true

tests/test_todo_lists.py

Lines changed: 12 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Unit tests for TodoList API endpoints."""
22

3-
from typing import Generator
3+
from collections.abc import Generator
44
from unittest.mock import MagicMock
55

66
import pytest
@@ -44,9 +44,7 @@ def client() -> TestClient:
4444
class TestIndex:
4545
"""Tests for GET /api/todolists endpoint."""
4646

47-
def test_returns_all_todo_lists(
48-
self, client: TestClient, mock_service: MagicMock
49-
) -> None:
47+
def test_returns_all_todo_lists(self, client: TestClient, mock_service: MagicMock) -> None:
5048
"""Test that index returns all todo lists."""
5149
# Arrange
5250
expected_todos = [
@@ -86,9 +84,7 @@ def test_returns_empty_list_when_no_todos(
8684
class TestShow:
8785
"""Tests for GET /api/todolists/{id} endpoint."""
8886

89-
def test_returns_todo_list_by_id(
90-
self, client: TestClient, mock_service: MagicMock
91-
) -> None:
87+
def test_returns_todo_list_by_id(self, client: TestClient, mock_service: MagicMock) -> None:
9288
"""Test that show returns a specific todo list."""
9389
# Arrange
9490
expected_todo = TodoList(id=1, name="Test list")
@@ -103,9 +99,7 @@ def test_returns_todo_list_by_id(
10399
assert response.json()["name"] == "Test list"
104100
mock_service.get.assert_called_once_with(1)
105101

106-
def test_returns_404_when_not_found(
107-
self, client: TestClient, mock_service: MagicMock
108-
) -> None:
102+
def test_returns_404_when_not_found(self, client: TestClient, mock_service: MagicMock) -> None:
109103
"""Test that show returns 404 when todo list doesn't exist."""
110104
# Arrange
111105
mock_service.get.return_value = None
@@ -122,9 +116,7 @@ def test_returns_404_when_not_found(
122116
class TestCreate:
123117
"""Tests for POST /api/todolists endpoint."""
124118

125-
def test_creates_new_todo_list(
126-
self, client: TestClient, mock_service: MagicMock
127-
) -> None:
119+
def test_creates_new_todo_list(self, client: TestClient, mock_service: MagicMock) -> None:
128120
"""Test that create successfully creates a new todo list."""
129121
# Arrange
130122
created_todo = TodoList(id=1, name="New list")
@@ -139,9 +131,7 @@ def test_creates_new_todo_list(
139131
assert response.json()["name"] == "New list"
140132
mock_service.create.assert_called_once()
141133

142-
def test_validates_required_fields(
143-
self, client: TestClient, mock_service: MagicMock
144-
) -> None:
134+
def test_validates_required_fields(self, client: TestClient, mock_service: MagicMock) -> None:
145135
"""Test that create validates required fields."""
146136
# Act
147137
response = client.post("/api/todolists", json={})
@@ -150,9 +140,7 @@ def test_validates_required_fields(
150140
assert response.status_code == 422
151141
mock_service.create.assert_not_called()
152142

153-
def test_validates_name_not_empty(
154-
self, client: TestClient, mock_service: MagicMock
155-
) -> None:
143+
def test_validates_name_not_empty(self, client: TestClient, mock_service: MagicMock) -> None:
156144
"""Test that create validates name is not empty."""
157145
# Act
158146
response = client.post("/api/todolists", json={"name": ""})
@@ -165,9 +153,7 @@ def test_validates_name_not_empty(
165153
class TestUpdate:
166154
"""Tests for PUT /api/todolists/{id} endpoint."""
167155

168-
def test_updates_existing_todo_list(
169-
self, client: TestClient, mock_service: MagicMock
170-
) -> None:
156+
def test_updates_existing_todo_list(self, client: TestClient, mock_service: MagicMock) -> None:
171157
"""Test that update successfully updates an existing todo list."""
172158
# Arrange
173159
updated_todo = TodoList(id=1, name="Updated list")
@@ -182,9 +168,7 @@ def test_updates_existing_todo_list(
182168
assert response.json()["name"] == "Updated list"
183169
mock_service.update.assert_called_once()
184170

185-
def test_returns_404_when_not_found(
186-
self, client: TestClient, mock_service: MagicMock
187-
) -> None:
171+
def test_returns_404_when_not_found(self, client: TestClient, mock_service: MagicMock) -> None:
188172
"""Test that update returns 404 when todo list doesn't exist."""
189173
# Arrange
190174
mock_service.update.return_value = None
@@ -197,9 +181,7 @@ def test_returns_404_when_not_found(
197181
assert "not found" in response.json()["detail"].lower()
198182
mock_service.update.assert_called_once()
199183

200-
def test_validates_required_fields(
201-
self, client: TestClient, mock_service: MagicMock
202-
) -> None:
184+
def test_validates_required_fields(self, client: TestClient, mock_service: MagicMock) -> None:
203185
"""Test that update validates required fields."""
204186
# Act
205187
response = client.put("/api/todolists/1", json={})
@@ -212,9 +194,7 @@ def test_validates_required_fields(
212194
class TestDelete:
213195
"""Tests for DELETE /api/todolists/{id} endpoint."""
214196

215-
def test_deletes_existing_todo_list(
216-
self, client: TestClient, mock_service: MagicMock
217-
) -> None:
197+
def test_deletes_existing_todo_list(self, client: TestClient, mock_service: MagicMock) -> None:
218198
"""Test that delete successfully deletes an existing todo list."""
219199
# Arrange
220200
mock_service.delete.return_value = True
@@ -227,9 +207,7 @@ def test_deletes_existing_todo_list(
227207
assert response.content == b""
228208
mock_service.delete.assert_called_once_with(1)
229209

230-
def test_returns_404_when_not_found(
231-
self, client: TestClient, mock_service: MagicMock
232-
) -> None:
210+
def test_returns_404_when_not_found(self, client: TestClient, mock_service: MagicMock) -> None:
233211
"""Test that delete returns 404 when todo list doesn't exist."""
234212
# Arrange
235213
mock_service.delete.return_value = False

0 commit comments

Comments
 (0)