refactor: enforce PLC0415 (import-outside-top-level) for source code
Enable ruff PLC0415 rule for all source files (tests excluded via per-file-ignores). Move 136 inline imports to top-level across 33 files. 8 imports suppressed with noqa for legitimate reasons: circular dependencies (client/__init__.py, context.py), optional dependency guards (app.py document processors, auth/userinfo_routes.py), and post-env-setup imports (smithery_main.py). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
81efa6e263
commit
a11ae9c027
@@ -11,6 +11,7 @@ import secrets
|
||||
import time
|
||||
from base64 import urlsafe_b64encode
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import urlparse as parse_url
|
||||
|
||||
import httpx
|
||||
import jwt
|
||||
@@ -153,8 +154,6 @@ async def oauth_login(request: Request) -> RedirectResponse | JSONResponse:
|
||||
# Replace internal Docker hostname with public URL
|
||||
public_issuer = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL")
|
||||
if public_issuer:
|
||||
from urllib.parse import urlparse as parse_url
|
||||
|
||||
internal_parsed = parse_url(oauth_config["nextcloud_host"])
|
||||
auth_parsed = parse_url(authorization_endpoint)
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -161,8 +162,6 @@ class ClientRegistry:
|
||||
True if valid, False otherwise
|
||||
"""
|
||||
# Parse the redirect URI
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed = urlparse(redirect_uri)
|
||||
|
||||
# Check against registered patterns
|
||||
|
||||
@@ -26,11 +26,13 @@ import secrets
|
||||
import time
|
||||
from base64 import urlsafe_b64encode
|
||||
from urllib.parse import urlencode
|
||||
from urllib.parse import urlparse as parse_url
|
||||
|
||||
import jwt
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import JSONResponse, RedirectResponse
|
||||
from starlette.responses import HTMLResponse, JSONResponse, RedirectResponse
|
||||
|
||||
from nextcloud_mcp_server.auth.browser_oauth_routes import oauth_login_callback
|
||||
from nextcloud_mcp_server.auth.client_registry import get_client_registry
|
||||
from nextcloud_mcp_server.auth.storage import RefreshTokenStorage
|
||||
|
||||
@@ -228,8 +230,6 @@ async def oauth_authorize(request: Request) -> RedirectResponse | JSONResponse:
|
||||
# IMPORTANT: Replace internal Docker hostname with public URL for browser access
|
||||
# The discovery endpoint returns http://app/apps/oidc/authorize (internal)
|
||||
# But browsers need http://localhost:8080/apps/oidc/authorize (public)
|
||||
from urllib.parse import urlparse as parse_url
|
||||
|
||||
public_issuer = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL")
|
||||
if public_issuer:
|
||||
# Parse internal and authorization endpoint to compare hostnames
|
||||
@@ -364,8 +364,6 @@ async def oauth_authorize_nextcloud(
|
||||
# Fix internal hostname for browser access
|
||||
public_issuer = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL")
|
||||
if public_issuer:
|
||||
from urllib.parse import urlparse as parse_url
|
||||
|
||||
internal_parsed = parse_url(oauth_config["nextcloud_host"])
|
||||
auth_parsed = parse_url(authorization_endpoint)
|
||||
|
||||
@@ -567,8 +565,6 @@ async def oauth_callback_nextcloud(request: Request):
|
||||
</html>
|
||||
"""
|
||||
|
||||
from starlette.responses import HTMLResponse
|
||||
|
||||
return HTMLResponse(content=success_html, status_code=200)
|
||||
|
||||
|
||||
@@ -633,10 +629,6 @@ async def oauth_callback(request: Request):
|
||||
elif flow_type == "browser":
|
||||
# Browser UI Login - establish browser session for /user/page access
|
||||
logger.info("Routing to browser login flow")
|
||||
from nextcloud_mcp_server.auth.browser_oauth_routes import (
|
||||
oauth_login_callback,
|
||||
)
|
||||
|
||||
return await oauth_login_callback(request)
|
||||
|
||||
else:
|
||||
|
||||
@@ -15,6 +15,7 @@ from mcp.shared.exceptions import McpError
|
||||
from mcp.types import ErrorData
|
||||
|
||||
from nextcloud_mcp_server.auth.storage import RefreshTokenStorage
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -66,8 +67,6 @@ def require_provisioning(func: Callable) -> Callable:
|
||||
|
||||
# Check if we're in token exchange mode - if so, skip provisioning check
|
||||
# In token exchange mode, tokens are exchanged per-request (no stored refresh tokens)
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
if hasattr(lifespan_ctx, "nextcloud_host") and settings.enable_token_exchange:
|
||||
# Token exchange mode - per-request exchange, no provisioning needed
|
||||
|
||||
@@ -9,6 +9,8 @@ from mcp.server.auth.provider import AccessToken
|
||||
from mcp.server.fastmcp import Context
|
||||
from mcp.server.fastmcp.utilities.context_injection import find_context_parameter
|
||||
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@@ -132,8 +134,6 @@ def require_scopes(*required_scopes: str):
|
||||
# Check if offline access is enabled
|
||||
# Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new)
|
||||
# and ENABLE_OFFLINE_ACCESS (deprecated) environment variables
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
enable_offline_access = settings.enable_offline_access
|
||||
|
||||
|
||||
@@ -13,11 +13,13 @@ import traceback
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from httpx import BasicAuth
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from starlette.authentication import requires
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from nextcloud_mcp_server.auth.permissions import is_nextcloud_admin
|
||||
from nextcloud_mcp_server.client import NextcloudClient
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
@@ -56,8 +58,6 @@ async def _get_authenticated_client_for_userinfo(request: Request) -> NextcloudC
|
||||
if not all([nextcloud_host, username, password]):
|
||||
raise RuntimeError("BasicAuth credentials not configured")
|
||||
|
||||
from httpx import BasicAuth
|
||||
|
||||
assert nextcloud_host is not None
|
||||
assert username is not None
|
||||
assert password is not None
|
||||
@@ -129,7 +129,9 @@ async def _get_processing_status(request: Request) -> dict[str, Any] | None:
|
||||
# Get Qdrant client and query indexed count
|
||||
indexed_count = 0
|
||||
try:
|
||||
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
|
||||
from nextcloud_mcp_server.vector.qdrant_client import ( # noqa: PLC0415
|
||||
get_qdrant_client,
|
||||
)
|
||||
|
||||
qdrant_client = await get_qdrant_client()
|
||||
|
||||
@@ -431,8 +433,6 @@ async def user_info_html(request: Request) -> HTMLResponse:
|
||||
# Check if user is admin (for Webhooks tab)
|
||||
is_admin = False
|
||||
try:
|
||||
from nextcloud_mcp_server.auth.permissions import is_nextcloud_admin
|
||||
|
||||
# Get authenticated Nextcloud client
|
||||
nc_client = await _get_authenticated_client_for_userinfo(request)
|
||||
is_admin = await is_nextcloud_admin(request, nc_client._client)
|
||||
@@ -471,8 +471,6 @@ async def user_info_html(request: Request) -> HTMLResponse:
|
||||
# Get Nextcloud host for generating links to apps (used by viz tab)
|
||||
# Use public issuer URL if available (for browser-accessible links),
|
||||
# otherwise fall back to NEXTCLOUD_HOST from settings
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
nextcloud_host_for_links = (
|
||||
os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL") or settings.nextcloud_host
|
||||
|
||||
@@ -18,16 +18,22 @@ from pathlib import Path
|
||||
import anyio
|
||||
import numpy as np
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
from qdrant_client.models import FieldCondition, Filter, MatchValue
|
||||
from starlette.authentication import requires
|
||||
from starlette.requests import Request
|
||||
from starlette.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from nextcloud_mcp_server.auth.userinfo_routes import (
|
||||
_get_authenticated_client_for_userinfo,
|
||||
)
|
||||
from nextcloud_mcp_server.config import get_settings
|
||||
from nextcloud_mcp_server.embedding.service import get_embedding_service
|
||||
from nextcloud_mcp_server.observability.tracing import trace_operation
|
||||
from nextcloud_mcp_server.search import (
|
||||
BM25HybridSearchAlgorithm,
|
||||
SemanticSearchAlgorithm,
|
||||
)
|
||||
from nextcloud_mcp_server.search.context import get_chunk_with_context
|
||||
from nextcloud_mcp_server.vector.pca import PCA
|
||||
from nextcloud_mcp_server.vector.placeholder import get_placeholder_filter
|
||||
from nextcloud_mcp_server.vector.qdrant_client import get_qdrant_client
|
||||
@@ -137,10 +143,6 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
|
||||
# Get authenticated HTTP client from session
|
||||
# In BasicAuth mode: uses username/password from session
|
||||
# In OAuth mode: uses access token from session
|
||||
from nextcloud_mcp_server.auth.userinfo_routes import (
|
||||
_get_authenticated_client_for_userinfo,
|
||||
)
|
||||
|
||||
with trace_operation("vector_viz.get_auth_client"):
|
||||
auth_client_ctx = await _get_authenticated_client_for_userinfo(request)
|
||||
|
||||
@@ -353,8 +355,6 @@ async def vector_visualization_search(request: Request) -> JSONResponse:
|
||||
)
|
||||
else:
|
||||
# Fallback: generate embedding if not available from search
|
||||
from nextcloud_mcp_server.embedding.service import get_embedding_service
|
||||
|
||||
embedding_service = get_embedding_service()
|
||||
query_embedding = await embedding_service.embed(query)
|
||||
logger.info(f"Generated query embedding (dimension={len(query_embedding)})")
|
||||
@@ -555,11 +555,6 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
|
||||
doc_id_int = int(doc_id)
|
||||
|
||||
# Get authenticated Nextcloud client
|
||||
from nextcloud_mcp_server.auth.userinfo_routes import (
|
||||
_get_authenticated_client_for_userinfo,
|
||||
)
|
||||
from nextcloud_mcp_server.search.context import get_chunk_with_context
|
||||
|
||||
# Use context expansion module to fetch chunk with surrounding context
|
||||
async with await _get_authenticated_client_for_userinfo(request) as nc_client:
|
||||
chunk_context = await get_chunk_with_context(
|
||||
@@ -594,8 +589,6 @@ async def chunk_context_endpoint(request: Request) -> JSONResponse:
|
||||
page_number = None
|
||||
if doc_type == "file":
|
||||
try:
|
||||
from qdrant_client.models import FieldCondition, Filter, MatchValue
|
||||
|
||||
settings = get_settings()
|
||||
qdrant_client = await get_qdrant_client()
|
||||
username = request.user.display_name
|
||||
|
||||
Reference in New Issue
Block a user