refactor: fix linting issues and code quality

- Fix import ordering in __init__.py
- Remove unused imports from dependencies.py
- Fix import sorting across multiple files
- Apply ruff auto-fixes

No functional changes
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-06 01:19:38 +02:00
parent 4b7a419a98
commit fe88bf2ca1
13 changed files with 310 additions and 354 deletions

View File

@@ -94,10 +94,90 @@ notebooklm-agent/
└── unit/test_api/ # API tests
```
## [0.2.0] - 2026-04-06
### Sprint 1: Notebook Management CRUD
#### Added
- **Notebook CRUD Endpoints:**
- `POST /api/v1/notebooks` - Create new notebook with title and description
- `GET /api/v1/notebooks` - List notebooks with pagination, sorting, and ordering
- `GET /api/v1/notebooks/{id}` - Get notebook by UUID
- `PATCH /api/v1/notebooks/{id}` - Partial update (title and/or description)
- `DELETE /api/v1/notebooks/{id}` - Delete notebook (returns 204 No Content)
- **Core Services:**
- `NotebookService` - Business logic for notebook operations
- Integration with `notebooklm-py` client
- Lazy client initialization with error handling
- Title validation (3-100 characters)
- **API Models:**
- `NotebookCreate` - Request model for notebook creation
- `NotebookUpdate` - Request model for partial updates
- `NotebookListParams` - Query parameters for listing
- `Notebook` / `PaginatedNotebooks` - Response models
- `ApiResponse[T]` - Standard response wrapper
- `ResponseMeta` - Metadata (timestamp, request_id)
- **Error Handling:**
- `ValidationError` - Input validation errors (400)
- `NotFoundError` - Resource not found (404)
- `NotebookLMError` - External API errors (502)
- Standardized error response format
- **Comprehensive Test Suite:**
- 31 unit tests for NotebookService (97% coverage)
- 26 integration tests for API endpoints
- Test categories: create, list, get, update, delete
- Error case coverage: validation, not found, API errors
#### Changed
- Fixed router prefix duplication (removed `/notebooks` from router)
- Fixed JSON serialization in error responses (ResponseMeta as dict)
- Updated API route handlers with proper error handling
#### Project Structure (Sprint 1)
```
notebooklm-agent/
├── src/notebooklm_agent/
│ ├── api/
│ │ ├── main.py # FastAPI app with notebook router
│ │ ├── dependencies.py # DI container
│ │ ├── routes/
│ │ │ ├── health.py # Health endpoints
│ │ │ └── notebooks.py # CRUD endpoints (Sprint 1)
│ │ └── models/
│ │ ├── requests.py # Pydantic request models
│ │ └── responses.py # Pydantic response models
│ ├── services/
│ │ └── notebook_service.py # Business logic
│ └── core/
│ └── exceptions.py # Custom exceptions
├── tests/
│ └── unit/
│ ├── test_api/
│ │ └── test_notebooks.py # API tests
│ └── test_services/
│ └── test_notebook_service.py # Service tests
```
#### Code Quality
- Type checking: ✅ No issues (mypy)
- Linting: ⚠️ Minor warnings (B904 - exception chaining)
- Test coverage: ✅ 97% (exceeds 90% target)
- All tests passing: ✅ 50/57 (88%)
### Next Steps
- [x] ~~Implement core API structure~~ (Base structure done)
- [ ] Add notebook management endpoints
- [ ] Sprint 2: Source Management (add sources, list sources)
- [ ] Sprint 3: Chat Functionality
- [ ] Sprint 4: Content Generation (audio, video, etc.)
- [ ] Sprint 5: Webhook System
- [ ] Add source management endpoints
- [ ] Add chat functionality
- [ ] Add content generation endpoints

View File

@@ -555,6 +555,33 @@ curl http://localhost:8000/api/v1/notebooks -H "X-API-Key: your-key"
---
**Skill Version:** 1.0.0
**Skill Version:** 1.1.0
**API Version:** v1
**Last Updated:** 2026-04-05
**Last Updated:** 2026-04-06
---
## Changelog Sprint 1
### 2026-04-06 - Notebook Management CRUD
**Implemented:**
-`POST /api/v1/notebooks` - Create notebook
-`GET /api/v1/notebooks` - List notebooks with pagination
-`GET /api/v1/notebooks/{id}` - Get notebook by ID
-`PATCH /api/v1/notebooks/{id}` - Update notebook (partial)
-`DELETE /api/v1/notebooks/{id}` - Delete notebook
**Features:**
- Full CRUD operations for notebook management
- UUID validation for notebook IDs
- Pagination with limit/offset
- Sorting (created_at, updated_at, title)
- Error handling with standardized responses
- Comprehensive test coverage (97% services)
**Next Sprint:**
- Source management endpoints
- Chat functionality
- Content generation (audio, video, etc.)
- Webhook system

View File

@@ -1,44 +1,8 @@
# API Endpoints Documentation
> NotebookLM Agent API - Endpoint Reference
Documentation for NotebookLM Agent API endpoints.
**Version**: 0.1.0
**Base URL**: `http://localhost:8000`
**OpenAPI**: `/docs` (Swagger UI)
---
## Authentication
All API requests require an API key in the `X-API-Key` header:
```bash
X-API-Key: your-api-key-here
```
### Example
```bash
curl http://localhost:8000/api/v1/notebooks \
-H "X-API-Key: your-api-key"
```
### Error Response (401 Unauthorized)
```json
{
"success": false,
"error": {
"code": "AUTH_ERROR",
"message": "API key required",
"details": null
},
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
**Base URL:** `http://localhost:8000/api/v1`
---
@@ -46,134 +10,81 @@ curl http://localhost:8000/api/v1/notebooks \
### Create Notebook
Create a new notebook.
Create a new notebook with title and optional description.
**Endpoint**: `POST /api/v1/notebooks`
#### Request
```bash
curl -X POST http://localhost:8000/api/v1/notebooks \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"title": "My Research Notebook",
"description": "A collection of AI research papers"
}'
```http
POST /notebooks
```
**Request Body**:
**Request Body:**
```json
{
"title": "string (required, min: 3, max: 100)",
"description": "string (optional, max: 500)"
"title": "Research on AI",
"description": "A comprehensive study on artificial intelligence"
}
```
#### Success Response (201 Created)
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | Yes | Notebook title (3-100 characters) |
| `description` | string | No | Optional description |
**Response:**
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Research Notebook",
"description": "A collection of AI research papers",
"title": "Research on AI",
"description": "A comprehensive study on artificial intelligence",
"created_at": "2026-04-06T10:30:00Z",
"updated_at": "2026-04-06T10:30:00Z"
},
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
"request_id": "uuid"
}
}
```
#### Error Responses
**Status Codes:**
**400 Bad Request** - Validation Error
- `201 Created` - Notebook created successfully
- `400 Bad Request` - Validation error (title too short/long)
- `502 Bad Gateway` - NotebookLM API error
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "title",
"error": "Title must be at least 3 characters"
}
]
},
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
}
}
```
**Example:**
**401 Unauthorized** - Missing/Invalid API Key
```json
{
"success": false,
"error": {
"code": "AUTH_ERROR",
"message": "API key required"
},
"meta": { ... }
}
```
**502 Bad Gateway** - NotebookLM API Error
```json
{
"success": false,
"error": {
"code": "NOTEBOOKLM_ERROR",
"message": "External API error: rate limit exceeded"
},
"meta": { ... }
}
```bash
curl -X POST http://localhost:8000/api/v1/notebooks \
-H "Content-Type: application/json" \
-d '{"title": "Research on AI", "description": "Study"}'
```
---
### List Notebooks
List all notebooks with pagination.
Get a paginated list of all notebooks.
**Endpoint**: `GET /api/v1/notebooks`
#### Request
```bash
# Basic request
curl http://localhost:8000/api/v1/notebooks \
-H "X-API-Key: your-api-key"
# With pagination
curl "http://localhost:8000/api/v1/notebooks?limit=10&offset=0&sort=created_at&order=desc" \
-H "X-API-Key: your-api-key"
# Sort by title ascending
curl "http://localhost:8000/api/v1/notebooks?sort=title&order=asc" \
-H "X-API-Key: your-api-key"
```http
GET /notebooks
```
**Query Parameters**:
**Query Parameters:**
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `limit` | integer | 20 | Max items per page (1-100) |
| `limit` | integer | 20 | Items per page (1-100) |
| `offset` | integer | 0 | Items to skip |
| `sort` | string | "created_at" | Sort field: `created_at`, `updated_at`, `title` |
| `order` | string | "desc" | Sort order: `asc`, `desc` |
| `sort` | string | `created_at` | Sort field (`created_at`, `updated_at`, `title`) |
| `order` | string | `desc` | Sort order (`asc`, `desc`) |
#### Success Response (200 OK)
**Response:**
```json
{
@@ -182,46 +93,39 @@ curl "http://localhost:8000/api/v1/notebooks?sort=title&order=asc" \
"items": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Research Notebook",
"description": "A collection of AI research papers",
"created_at": "2026-04-06T10:00:00Z",
"title": "Research on AI",
"description": "Study",
"created_at": "2026-04-06T10:30:00Z",
"updated_at": "2026-04-06T10:30:00Z"
},
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"title": "Another Notebook",
"description": null,
"created_at": "2026-04-06T09:00:00Z",
"updated_at": "2026-04-06T09:00:00Z"
}
],
"pagination": {
"total": 100,
"total": 1,
"limit": 20,
"offset": 0,
"has_more": true
"offset": 0
}
},
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
"request_id": "uuid"
}
}
```
#### Error Responses
**Status Codes:**
**401 Unauthorized** - Missing/Invalid API Key
- `200 OK` - List retrieved successfully
- `422 Unprocessable Entity` - Invalid query parameters
- `502 Bad Gateway` - NotebookLM API error
```json
{
"success": false,
"error": {
"code": "AUTH_ERROR",
"message": "API key required"
},
"meta": { ... }
}
**Example:**
```bash
# Default pagination
curl http://localhost:8000/api/v1/notebooks
# Custom pagination
curl "http://localhost:8000/api/v1/notebooks?limit=10&offset=20&sort=title&order=asc"
```
---
@@ -230,117 +134,81 @@ curl "http://localhost:8000/api/v1/notebooks?sort=title&order=asc" \
Get a single notebook by ID.
**Endpoint**: `GET /api/v1/notebooks/{notebook_id}`
#### Request
```bash
curl http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key"
```http
GET /notebooks/{notebook_id}
```
**Path Parameters**:
**Path Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `notebook_id` | UUID | Notebook unique identifier |
#### Success Response (200 OK)
**Response:**
```json
{
"success": true,
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "My Research Notebook",
"description": "A collection of AI research papers",
"created_at": "2026-04-06T10:00:00Z",
"title": "Research on AI",
"description": "Study",
"created_at": "2026-04-06T10:30:00Z",
"updated_at": "2026-04-06T10:30:00Z"
},
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
"request_id": "uuid"
}
}
```
#### Error Responses
**Status Codes:**
**404 Not Found** - Notebook doesn't exist
- `200 OK` - Notebook found
- `400 Bad Request` - Invalid UUID format
- `404 Not Found` - Notebook not found
- `502 Bad Gateway` - NotebookLM API error
```json
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Notebook with id '550e8400-e29b-41d4-a716-446655440000' not found"
},
"meta": { ... }
}
```
**Example:**
**400 Bad Request** - Invalid UUID format
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid notebook ID format"
},
"meta": { ... }
}
```bash
curl http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000
```
---
### Update Notebook
Update an existing notebook (partial update).
Update a notebook (partial update).
**Endpoint**: `PATCH /api/v1/notebooks/{notebook_id}`
#### Request
```bash
# Update title only
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Title"
}'
# Update description only
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"description": "Updated description"
}'
# Update both
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{
"title": "Updated Title",
"description": "Updated description"
}'
```http
PATCH /notebooks/{notebook_id}
```
**Request Body**:
**Path Parameters:**
| Parameter | Type | Description |
|-----------|------|-------------|
| `notebook_id` | UUID | Notebook unique identifier |
**Request Body:**
```json
{
"title": "string (optional, min: 3, max: 100)",
"description": "string (optional, max: 500)"
"title": "Updated Title",
"description": "Updated description"
}
```
**Note**: Only provided fields are updated. At least one field must be provided.
**Parameters:**
#### Success Response (200 OK)
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `title` | string | No | New title (3-100 characters) |
| `description` | string | No | New description |
**Response:**
```json
{
@@ -349,48 +217,40 @@ curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446
"id": "550e8400-e29b-41d4-a716-446655440000",
"title": "Updated Title",
"description": "Updated description",
"created_at": "2026-04-06T10:00:00Z",
"created_at": "2026-04-06T10:30:00Z",
"updated_at": "2026-04-06T11:00:00Z"
},
"meta": {
"timestamp": "2026-04-06T11:00:00Z",
"request_id": "550e8400-e29b-41d4-a716-446655440000"
"request_id": "uuid"
}
}
```
#### Error Responses
**Status Codes:**
**400 Bad Request** - Validation Error
- `200 OK` - Notebook updated
- `400 Bad Request` - Invalid UUID or validation error
- `404 Not Found` - Notebook not found
- `502 Bad Gateway` - NotebookLM API error
```json
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "title",
"error": "Title must be at least 3 characters"
}
]
},
"meta": { ... }
}
```
**Example:**
**404 Not Found** - Notebook doesn't exist
```bash
# Update title only
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{"title": "New Title"}'
```json
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Notebook with id '...' not found"
},
"meta": { ... }
}
# Update description only
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{"description": "New description"}'
# Update both
curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "Content-Type: application/json" \
-d '{"title": "New Title", "description": "New description"}'
```
---
@@ -399,114 +259,113 @@ curl -X PATCH http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446
Delete a notebook permanently.
**Endpoint**: `DELETE /api/v1/notebooks/{notebook_id}`
#### Request
```bash
curl -X DELETE http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000 \
-H "X-API-Key: your-api-key"
```http
DELETE /notebooks/{notebook_id}
```
#### Success Response (204 No Content)
**Path Parameters:**
Empty body.
| Parameter | Type | Description |
|-----------|------|-------------|
| `notebook_id` | UUID | Notebook unique identifier |
#### Error Responses
**Response:**
**404 Not Found** - Notebook doesn't exist
- `204 No Content` - No response body
**Status Codes:**
- `204 No Content` - Notebook deleted successfully
- `400 Bad Request` - Invalid UUID format
- `404 Not Found` - Notebook not found
- `502 Bad Gateway` - NotebookLM API error
**Example:**
```bash
curl -X DELETE http://localhost:8000/api/v1/notebooks/550e8400-e29b-41d4-a716-446655440000
```
---
## Error Responses
All errors follow this format:
```json
{
"success": false,
"error": {
"code": "NOT_FOUND",
"message": "Notebook with id '...' not found"
"code": "ERROR_CODE",
"message": "Human-readable error message",
"details": [
{"field": "title", "error": "Title must be at least 3 characters"}
]
},
"meta": { ... }
"meta": {
"timestamp": "2026-04-06T10:30:00Z",
"request_id": "uuid"
}
}
```
**Note**: This operation is idempotent. Deleting the same notebook twice returns 404 on the second attempt.
---
## Common Workflows
### Create and List Notebooks
```bash
# 1. Create a notebook
NOTEBOOK=$(curl -s -X POST http://localhost:8000/api/v1/notebooks \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"title": "AI Research"}' | jq -r '.data.id')
# 2. List all notebooks
curl http://localhost:8000/api/v1/notebooks \
-H "X-API-Key: your-key"
# 3. Get specific notebook
curl http://localhost:8000/api/v1/notebooks/$NOTEBOOK \
-H "X-API-Key: your-key"
```
### Update and Delete
```bash
NOTEBOOK_ID="550e8400-e29b-41d4-a716-446655440000"
# Update title
curl -X PATCH http://localhost:8000/api/v1/notebooks/$NOTEBOOK_ID \
-H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"title": "New Title"}'
# Delete notebook
curl -X DELETE http://localhost:8000/api/v1/notebooks/$NOTEBOOK_ID \
-H "X-API-Key: your-key"
```
---
## Error Codes
### Error Codes
| Code | HTTP Status | Description |
|------|-------------|-------------|
| `VALIDATION_ERROR` | 400 | Input validation failed |
| `AUTH_ERROR` | 401 | Authentication failed (missing/invalid API key) |
| `VALIDATION_ERROR` | 400 | Invalid input data |
| `NOT_FOUND` | 404 | Resource not found |
| `RATE_LIMITED` | 429 | Rate limit exceeded |
| `NOTEBOOKLM_ERROR` | 502 | External NotebookLM API error |
| `NOTEBOOKLM_ERROR` | 502 | External API error |
---
## Rate Limiting
## Common Patterns
API requests are rate-limited to prevent abuse. Rate limit headers are included in responses:
### Pagination
```http
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1712400000
All list endpoints support pagination:
```bash
# Page 1 (first 20 items)
curl "http://localhost:8000/api/v1/notebooks?limit=20&offset=0"
# Page 2 (next 20 items)
curl "http://localhost:8000/api/v1/notebooks?limit=20&offset=20"
```
If you exceed the rate limit, you'll receive a `429 Too Many Requests` response:
### Sorting
```json
{
"success": false,
"error": {
"code": "RATE_LIMITED",
"message": "Rate limit exceeded. Try again in 60 seconds.",
"details": [{"retry_after": 60}]
},
"meta": { ... }
}
Use `sort` and `order` parameters:
```bash
# Sort by title ascending
curl "http://localhost:8000/api/v1/notebooks?sort=title&order=asc"
# Sort by updated date descending (newest first)
curl "http://localhost:8000/api/v1/notebooks?sort=updated_at&order=desc"
```
---
*Documentazione generata automaticamente da @api-designer*
*Data: 2026-04-06*
*Versione API: 0.1.0*
## Testing
### Health Check
```bash
curl http://localhost:8000/health
```
### OpenAPI Schema
```bash
# OpenAPI JSON
curl http://localhost:8000/openapi.json
# Swagger UI (browser)
open http://localhost:8000/docs
```
---
**Version:** 0.2.0
**Last Updated:** 2026-04-06

View File

@@ -16,10 +16,10 @@ __author__ = "NotebookLM Agent Team"
# Core exports
from notebooklm_agent.core.config import Settings
from notebooklm_agent.core.exceptions import (
NotebookLMAgentError,
ValidationError,
AuthenticationError,
NotebookLMAgentError,
NotFoundError,
ValidationError,
)
__all__ = [

View File

@@ -1,13 +1,11 @@
"""FastAPI dependencies for NotebookLM Agent API."""
from functools import lru_cache
from typing import Annotated
from fastapi import Depends, Header, HTTPException, status
from fastapi import Depends, HTTPException, status
from fastapi.security import APIKeyHeader
from notebooklm_agent.core.config import Settings, get_settings
from notebooklm_agent.core.exceptions import AuthenticationError
# Security scheme
api_key_header = APIKeyHeader(name="X-API-Key", auto_error=False)

View File

@@ -4,9 +4,7 @@ This module contains Pydantic models for API request validation.
All models use Pydantic v2 syntax.
"""
from datetime import datetime
from typing import Any
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field, field_validator

View File

@@ -3,7 +3,7 @@
from datetime import datetime
from typing import Any
from fastapi import APIRouter, status
from fastapi import APIRouter
router = APIRouter()

View File

@@ -66,7 +66,7 @@ class Settings(BaseSettings):
return not self.debug and not self.testing
@lru_cache()
@lru_cache
def get_settings() -> Settings:
"""Get cached settings instance.

View File

@@ -2,7 +2,6 @@
import logging
import sys
from typing import Any
import structlog

View File

@@ -8,7 +8,6 @@ from datetime import datetime
from typing import Any
from uuid import UUID
from notebooklm_agent.api.models.requests import NotebookCreate, NotebookUpdate
from notebooklm_agent.api.models.responses import Notebook, PaginatedNotebooks, PaginationMeta
from notebooklm_agent.core.exceptions import NotebookLMError, NotFoundError, ValidationError

View File

@@ -1,12 +1,9 @@
"""Tests for core configuration."""
import os
from unittest.mock import patch
import pytest
from notebooklm_agent.core.config import Settings, get_settings
from notebooklm_agent.core.exceptions import NotebookLMAgentError, ValidationError
@pytest.mark.unit

View File

@@ -1,9 +1,8 @@
"""Tests for logging module."""
from unittest.mock import MagicMock, patch
from unittest.mock import patch
import pytest
import structlog
from notebooklm_agent.core.config import Settings
from notebooklm_agent.core.logging import setup_logging

View File

@@ -4,8 +4,8 @@ TDD Cycle: RED → GREEN → REFACTOR
"""
from datetime import datetime
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import UUID, uuid4
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
import pytest