feat(schemas): T23 add Pydantic API key schemas

- Add ApiKeyCreate schema with OpenRouter key format validation
- Add ApiKeyUpdate schema for partial updates
- Add ApiKeyResponse schema (excludes key value for security)
- Add ApiKeyListResponse schema for pagination
- Export schemas from __init__.py
- 100% coverage on new module (23 tests)

Refs: T23
This commit is contained in:
Luca Sacchi Ricciardi
2026-04-07 14:28:03 +02:00
parent b4fbb74113
commit 2e4c1bb1e5
5 changed files with 1024 additions and 1 deletions

View File

@@ -1,4 +1,10 @@
"""Schemas package for OpenRouter Monitor."""
from openrouter_monitor.schemas.api_key import (
ApiKeyCreate,
ApiKeyListResponse,
ApiKeyResponse,
ApiKeyUpdate,
)
from openrouter_monitor.schemas.auth import (
TokenData,
TokenResponse,
@@ -13,4 +19,8 @@ __all__ = [
"UserResponse",
"TokenResponse",
"TokenData",
"ApiKeyCreate",
"ApiKeyUpdate",
"ApiKeyResponse",
"ApiKeyListResponse",
]

View File

@@ -0,0 +1,138 @@
"""API Key Pydantic schemas.
T23: Pydantic schemas for API key management.
"""
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, ConfigDict, Field, field_validator
class ApiKeyCreate(BaseModel):
"""Schema for creating a new API key.
Attributes:
name: Human-readable name for the key (1-100 chars)
key: OpenRouter API key (must start with 'sk-or-v1-')
"""
name: str = Field(
...,
min_length=1,
max_length=100,
description="Human-readable name for the API key",
examples=["Production Key"]
)
key: str = Field(
...,
description="OpenRouter API key",
examples=["sk-or-v1-abc123..."]
)
@field_validator('key')
@classmethod
def validate_key_format(cls, v: str) -> str:
"""Validate OpenRouter API key format.
Args:
v: The API key value to validate
Returns:
The API key if valid
Raises:
ValueError: If key doesn't start with 'sk-or-v1-'
"""
if not v or not v.strip():
raise ValueError("API key cannot be empty")
if not v.startswith('sk-or-v1-'):
raise ValueError("API key must start with 'sk-or-v1-'")
return v
class ApiKeyUpdate(BaseModel):
"""Schema for updating an existing API key.
All fields are optional - only provided fields will be updated.
Attributes:
name: New name for the key (optional, 1-100 chars)
is_active: Whether the key should be active (optional)
"""
name: Optional[str] = Field(
default=None,
min_length=1,
max_length=100,
description="New name for the API key",
examples=["Updated Key Name"]
)
is_active: Optional[bool] = Field(
default=None,
description="Whether the key should be active",
examples=[True, False]
)
class ApiKeyResponse(BaseModel):
"""Schema for API key response (returned to client).
Note: The actual API key value is NEVER included in responses
for security reasons.
Attributes:
id: API key ID
name: API key name
is_active: Whether the key is active
created_at: When the key was created
last_used_at: When the key was last used (None if never used)
"""
model_config = ConfigDict(from_attributes=True)
id: int = Field(
...,
description="API key ID",
examples=[1]
)
name: str = Field(
...,
description="API key name",
examples=["Production Key"]
)
is_active: bool = Field(
...,
description="Whether the key is active",
examples=[True]
)
created_at: datetime = Field(
...,
description="When the key was created",
examples=["2024-01-01T12:00:00"]
)
last_used_at: Optional[datetime] = Field(
default=None,
description="When the key was last used",
examples=["2024-01-02T15:30:00"]
)
class ApiKeyListResponse(BaseModel):
"""Schema for paginated list of API keys.
Attributes:
items: List of API key responses
total: Total number of keys (for pagination)
"""
items: List[ApiKeyResponse] = Field(
...,
description="List of API keys"
)
total: int = Field(
...,
description="Total number of API keys",
examples=[10]
)