Skip to content

Commit e277c53

Browse files
Fix: CI/CD Quality Checks (Linting & Formatting)
- Black: Auto-formatted 3 files (search.py, ingestion.py, test_search.py). - Ruff: Fixed 86 linting errors (55 auto, 31 manual). - Added missing imports (select, AsyncGenerator). - Fixed exception handling (raise from err). - Fixed None comparisons (is_(None)). - Removed commented code. - Simplified return statements and nested if. - Fixed test file import ordering. - CI: Added Black format check to workflow. - Tests: 30/30 passed, 80% coverage maintained.
1 parent 8294dc3 commit e277c53

File tree

22 files changed

+100
-104
lines changed

22 files changed

+100
-104
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ jobs:
4141
run: |
4242
poetry install
4343
44+
- name: Check code formatting with Black
45+
run: |
46+
poetry run black --check app tests
47+
4448
- name: Lint with Ruff
4549
run: |
4650
poetry run ruff check .

app/api/deps.py

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
"""API dependencies."""
2-
from typing import Annotated, AsyncGenerator
2+
from collections.abc import AsyncGenerator
3+
from typing import Annotated
34

4-
from fastapi import Depends, HTTPException, status, Security
5-
from fastapi.security import OAuth2PasswordBearer, APIKeyHeader
5+
from fastapi import Depends, HTTPException, Security, status
6+
from fastapi.security import APIKeyHeader, OAuth2PasswordBearer
67
from jose import JWTError
78
from pydantic import ValidationError
89
from sqlalchemy.ext.asyncio import AsyncSession
910

10-
from app.core import security, auth_client
11+
from app.core import auth_client, security
1112
from app.core.config import settings
1213
from app.db.session import SessionLocal
13-
from app.models.user import User
1414
from app.models.client import Client
15-
from app.repositories.user import user_repo
15+
from app.models.user import User
1616
from app.repositories.api_key import api_key_repo
17+
from app.repositories.user import user_repo
1718
from app.schemas.token import TokenPayload
18-
from app.schemas.user import UserResponse
1919

2020
# OAuth2 scheme for JWT token in Authorization header
2121
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.api_v1_str}/admin/auth/login")
@@ -52,11 +52,11 @@ async def get_current_user(session: SessionDep, token: TokenDep) -> User:
5252
headers={"WWW-Authenticate": "Bearer"},
5353
)
5454
token_data = TokenPayload(**payload)
55-
except (JWTError, ValidationError):
55+
except (JWTError, ValidationError) as err:
5656
raise HTTPException(
5757
status_code=status.HTTP_403_FORBIDDEN,
5858
detail="Could not validate credentials",
59-
)
59+
) from err
6060

6161
user = await user_repo.get(session, token_data.sub) # type: ignore # sub is UUID in string
6262
if not user:
@@ -91,10 +91,6 @@ async def get_current_client(
9191
detail="Invalid or expired API Key",
9292
)
9393

94-
# Update last used (fire and forget / async task in real app)
95-
# db_key.last_used_at = datetime.utcnow()
96-
# await session.commit()
97-
9894
if not db_key.client.is_license_valid:
9995
raise HTTPException(
10096
status_code=status.HTTP_403_FORBIDDEN,
@@ -109,7 +105,7 @@ def get_content_service() -> "ContentService":
109105
Dependency to get content service instance.
110106
In a real app with more complex deps, this could initialize the service.
111107
"""
112-
from app.services.content import ContentService, content_service as _content_service
108+
from app.services.content import content_service as _content_service
113109

114110
return _content_service
115111

app/api/v1/admin/auth.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
from fastapi import APIRouter, Depends, HTTPException, status
66
from fastapi.security import OAuth2PasswordRequestForm
7-
from sqlalchemy.ext.asyncio import AsyncSession
87

98
from app.api.deps import SessionDep
109
from app.core import security

app/api/v1/content/search.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@ async def search_content(
2121
) -> Any:
2222
"""
2323
Search for content using semantic similarity (RAG).
24-
24+
2525
Returns the most relevant snippets for the given query.
2626
Access is restricted to the authenticated client's private data + shared data.
2727
"""
28-
results = await search_service.search_content(
28+
return await search_service.search_content(
2929
session=db, query=query, client=current_client, limit=limit
3030
)
31-
return results

app/api/v1/router.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from fastapi import APIRouter
33

44
from app.api.v1.admin import auth
5-
from app.api.v1.content import guides, topics, search
5+
from app.api.v1.content import guides, search, topics
66

77
api_router = APIRouter()
88

app/core/config.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,12 @@ def parse_cors_origins(cls, v: str) -> list[str]:
7878
@classmethod
7979
def validate_production_secrets(cls, v: str, info: ValidationInfo) -> str:
8080
"""Ensure default secrets are not used in production."""
81-
if info.data.get("environment") == "production":
82-
# Check if value contains default insecure strings
83-
if "insecure" in v:
84-
name = info.field_name
85-
raise ValueError(
86-
f"Production configuration error: {name} must be changed from default insecure value."
87-
)
81+
if info.data.get("environment") == "production" and "insecure" in v:
82+
name = info.field_name
83+
raise ValueError(
84+
f"Production configuration error: {name} must be changed "
85+
f"from default insecure value."
86+
)
8887
return v
8988

9089
@property

app/models/api_key.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""API Key model for B2B authentication."""
22
import uuid
3-
from datetime import datetime, timezone
3+
from datetime import UTC, datetime
44
from typing import TYPE_CHECKING
55
from uuid import UUID
66

@@ -39,7 +39,7 @@ class APIKey(Base):
3939
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False, index=True)
4040
last_used_at: Mapped[datetime | None] = mapped_column(DateTime, nullable=True)
4141
created_at: Mapped[datetime] = mapped_column(
42-
DateTime, default=lambda: datetime.now(timezone.utc)
42+
DateTime, default=lambda: datetime.now(UTC)
4343
)
4444
expires_at: Mapped[datetime | None] = mapped_column(
4545
DateTime, nullable=True

app/models/client.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Client model for B2B licensing."""
22
import enum
33
import uuid
4-
from datetime import datetime, timezone
4+
from datetime import UTC, datetime
55
from typing import TYPE_CHECKING
66

77
from sqlalchemy import Boolean, Column, DateTime, String, text
@@ -41,11 +41,11 @@ class Client(Base):
4141
)
4242
is_active = Column(Boolean, default=True, nullable=False, index=True)
4343
license_expires_at = Column(DateTime, nullable=True) # None = permanent
44-
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
44+
created_at = Column(DateTime, default=lambda: datetime.now(UTC), nullable=False)
4545
updated_at = Column(
4646
DateTime,
47-
default=lambda: datetime.now(timezone.utc),
48-
onupdate=lambda: datetime.now(timezone.utc),
47+
default=lambda: datetime.now(UTC),
48+
onupdate=lambda: datetime.now(UTC),
4949
nullable=False,
5050
)
5151

app/models/guide.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Guide model - main documentation files."""
22
import enum
33
import uuid
4-
from datetime import datetime, timezone
4+
from datetime import UTC, datetime
55
from typing import TYPE_CHECKING
66

77
from sqlalchemy import Column, DateTime, ForeignKey, Index, Integer, String, Text, text
@@ -58,11 +58,11 @@ class Guide(Base):
5858
SQLEnum(ComplexityLevel), nullable=False, index=True
5959
)
6060
estimated_read_time = Column(Integer, default=0) # minutes
61-
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
61+
created_at = Column(DateTime, default=lambda: datetime.now(UTC), nullable=False)
6262
updated_at = Column(
6363
DateTime,
64-
default=lambda: datetime.now(timezone.utc),
65-
onupdate=lambda: datetime.now(timezone.utc),
64+
default=lambda: datetime.now(UTC),
65+
onupdate=lambda: datetime.now(UTC),
6666
nullable=False,
6767
)
6868
# Relationships

app/models/section.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Section model - logical blocks within guides."""
22
import enum
33
import uuid
4-
from datetime import datetime, timezone
4+
from datetime import UTC, datetime
55
from typing import TYPE_CHECKING
66

77
from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text, text
@@ -47,11 +47,11 @@ class Section(Base):
4747
content = Column(Text, nullable=False) # Markdown content
4848
order = Column(Integer, nullable=False, index=True) # For sequencing
4949
section_type: Mapped[SectionType] = mapped_column(SQLEnum(SectionType), nullable=False)
50-
created_at = Column(DateTime, default=lambda: datetime.now(timezone.utc), nullable=False)
50+
created_at = Column(DateTime, default=lambda: datetime.now(UTC), nullable=False)
5151
updated_at = Column(
5252
DateTime,
53-
default=lambda: datetime.now(timezone.utc),
54-
onupdate=lambda: datetime.now(timezone.utc),
53+
default=lambda: datetime.now(UTC),
54+
onupdate=lambda: datetime.now(UTC),
5555
nullable=False,
5656
)
5757
# Relationships

0 commit comments

Comments
 (0)