diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 160abd9..6d9d903 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -421,13 +421,13 @@ repos: description: The uncompromising Python code formatter. language_version: python3 - # - repo: https://github.com/pycqa/isort - # rev: 6.0.1 - # hooks: - # - id: isort - # name: 🐍 isort - Import Sorter - # description: A Python utility / library to sort imports. - # args: [--profile=black] + - repo: https://github.com/pycqa/isort + rev: 6.0.1 + hooks: + - id: isort + name: 🐍 isort - Import Sorter + description: A Python utility / library to sort imports. + args: [--profile=black] # - repo: https://github.com/asottile/pyupgrade # rev: v3.20.0 diff --git a/cforge/commands/deploy/deploy.py b/cforge/commands/deploy/deploy.py index 4d1f578..8b0af49 100644 --- a/cforge/commands/deploy/deploy.py +++ b/cforge/commands/deploy/deploy.py @@ -10,7 +10,7 @@ # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console diff --git a/cforge/commands/metrics/metrics.py b/cforge/commands/metrics/metrics.py index 770b599..e70ab6d 100644 --- a/cforge/commands/metrics/metrics.py +++ b/cforge/commands/metrics/metrics.py @@ -10,7 +10,7 @@ # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import make_authenticated_request from cforge.common.render import print_json diff --git a/cforge/commands/resources/a2a.py b/cforge/commands/resources/a2a.py index 1dde748..b98b5ef 100644 --- a/cforge/commands/resources/a2a.py +++ b/cforge/commands/resources/a2a.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import A2AAgentCreate, A2AAgentUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import A2AAgentCreate, A2AAgentUpdate def a2a_list( diff --git a/cforge/commands/resources/mcp_servers.py b/cforge/commands/resources/mcp_servers.py index 8973e26..4c84919 100644 --- a/cforge/commands/resources/mcp_servers.py +++ b/cforge/commands/resources/mcp_servers.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import GatewayCreate, GatewayUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import GatewayCreate, GatewayUpdate def mcp_servers_list( diff --git a/cforge/commands/resources/plugins.py b/cforge/commands/resources/plugins.py index 5baa698..feb4d04 100644 --- a/cforge/commands/resources/plugins.py +++ b/cforge/commands/resources/plugins.py @@ -19,9 +19,9 @@ # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console -from cforge.common.errors import AuthenticationError, CaseInsensitiveEnum, CLIError, handle_exception +from cforge.common.errors import AuthenticationError, CLIError, CaseInsensitiveEnum, handle_exception from cforge.common.http import make_authenticated_request from cforge.common.render import print_json, print_table diff --git a/cforge/commands/resources/prompts.py b/cforge/commands/resources/prompts.py index b3b0d83..68d69bc 100644 --- a/cforge/commands/resources/prompts.py +++ b/cforge/commands/resources/prompts.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Any, Dict, Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import PromptCreate, PromptUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import PromptCreate, PromptUpdate def prompts_list( diff --git a/cforge/commands/resources/resources.py b/cforge/commands/resources/resources.py index 05137cd..e5dfa53 100644 --- a/cforge/commands/resources/resources.py +++ b/cforge/commands/resources/resources.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Any, Dict, Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import ResourceCreate, ResourceUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import ResourceCreate, ResourceUpdate def resources_list( diff --git a/cforge/commands/resources/tools.py b/cforge/commands/resources/tools.py index 0c88bb4..557867a 100644 --- a/cforge/commands/resources/tools.py +++ b/cforge/commands/resources/tools.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Any, Dict, Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import ToolCreate, ToolUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import CLIError, handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_json_schema, prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import ToolCreate, ToolUpdate def tools_list( diff --git a/cforge/commands/resources/virtual_servers.py b/cforge/commands/resources/virtual_servers.py index 39ecf88..cf80558 100644 --- a/cforge/commands/resources/virtual_servers.py +++ b/cforge/commands/resources/virtual_servers.py @@ -8,20 +8,22 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party import typer # First-Party +from mcpgateway.schemas import ServerCreate, ServerUpdate + +# Local from cforge.common.console import get_console from cforge.common.errors import handle_exception from cforge.common.http import make_authenticated_request from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table -from mcpgateway.schemas import ServerCreate, ServerUpdate def _fixup_payload(data: dict) -> dict: diff --git a/cforge/commands/server/run.py b/cforge/commands/server/run.py index 23f1060..883ba19 100644 --- a/cforge/commands/server/run.py +++ b/cforge/commands/server/run.py @@ -12,17 +12,17 @@ """ # Standard +from typing import List, Optional import atexit import multiprocessing import os import time -from typing import List, Optional # Third-Party import requests import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import make_authenticated_request @@ -163,7 +163,8 @@ def run( args.append("--jsonResponse") # Import top-level translate here to avoid undesirable initialization - # Third Party + + # First-Party from mcpgateway.translate import main as translate_main # Launch the translation wrapper in a subprocess diff --git a/cforge/commands/server/serve.py b/cforge/commands/server/serve.py index 76dc308..14a57d5 100644 --- a/cforge/commands/server/serve.py +++ b/cforge/commands/server/serve.py @@ -14,7 +14,7 @@ import typer import uvicorn -# First-Party +# Local from cforge.config import get_settings, set_serve_settings # --------------------------------------------------------------------------- diff --git a/cforge/commands/settings/config_schema.py b/cforge/commands/settings/config_schema.py index 1d95797..623c2fb 100644 --- a/cforge/commands/settings/config_schema.py +++ b/cforge/commands/settings/config_schema.py @@ -8,14 +8,14 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.render import print_json from cforge.config import get_settings diff --git a/cforge/commands/settings/export.py b/cforge/commands/settings/export.py index 3937c1e..0768bba 100644 --- a/cforge/commands/settings/export.py +++ b/cforge/commands/settings/export.py @@ -9,14 +9,14 @@ # Standard from datetime import datetime -import json from pathlib import Path from typing import Any, Dict, Optional +import json # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import get_base_url, make_authenticated_request diff --git a/cforge/commands/settings/import_cmd.py b/cforge/commands/settings/import_cmd.py index d02b3e6..42a3522 100644 --- a/cforge/commands/settings/import_cmd.py +++ b/cforge/commands/settings/import_cmd.py @@ -8,14 +8,14 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import make_authenticated_request diff --git a/cforge/commands/settings/login.py b/cforge/commands/settings/login.py index 10294e1..ba238b5 100644 --- a/cforge/commands/settings/login.py +++ b/cforge/commands/settings/login.py @@ -11,7 +11,7 @@ import requests import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import get_base_url, get_token_file, save_token diff --git a/cforge/commands/settings/logout.py b/cforge/commands/settings/logout.py index c1d86da..008acfb 100644 --- a/cforge/commands/settings/logout.py +++ b/cforge/commands/settings/logout.py @@ -7,7 +7,7 @@ CLI command: logout """ -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import get_token_file diff --git a/cforge/commands/settings/profiles.py b/cforge/commands/settings/profiles.py index d39f5ae..ef60cde 100644 --- a/cforge/commands/settings/profiles.py +++ b/cforge/commands/settings/profiles.py @@ -9,27 +9,27 @@ # Standard from datetime import datetime -import json from pathlib import Path +from typing import Optional +import json import secrets import string -from typing import Optional # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console from cforge.common.prompting import prompt_for_schema from cforge.common.render import print_json, print_table from cforge.config import get_settings from cforge.profile_utils import ( AuthProfile, + ProfileStore, get_active_profile, get_all_profiles, get_profile, load_profile_store, - ProfileStore, save_profile_store, set_active_profile, ) diff --git a/cforge/commands/settings/support_bundle.py b/cforge/commands/settings/support_bundle.py index da9f930..793e27d 100644 --- a/cforge/commands/settings/support_bundle.py +++ b/cforge/commands/settings/support_bundle.py @@ -14,7 +14,7 @@ # Third-Party import typer -# First-Party +# Local from cforge.common.console import get_console diff --git a/cforge/commands/settings/version.py b/cforge/commands/settings/version.py index ae63438..0ea3339 100644 --- a/cforge/commands/settings/version.py +++ b/cforge/commands/settings/version.py @@ -8,9 +8,11 @@ """ # First-Party +from mcpgateway import __version__ + +# Local from cforge.common.console import get_console from cforge.common.http import make_authenticated_request -from mcpgateway import __version__ def version() -> None: diff --git a/cforge/commands/settings/whoami.py b/cforge/commands/settings/whoami.py index ed91743..9a4d607 100644 --- a/cforge/commands/settings/whoami.py +++ b/cforge/commands/settings/whoami.py @@ -7,7 +7,7 @@ CLI command: whoami """ -# First-Party +# Local from cforge.common.console import get_console from cforge.common.http import get_token_file, load_token from cforge.config import get_settings diff --git a/cforge/common/console.py b/cforge/common/console.py index 5e3ab60..36d050e 100644 --- a/cforge/common/console.py +++ b/cforge/common/console.py @@ -9,8 +9,10 @@ configuration without repeated construction. """ +# Standard from functools import lru_cache +# Third-Party from rich.console import Console import typer diff --git a/cforge/common/errors.py b/cforge/common/errors.py index 58e96e7..c21fe90 100644 --- a/cforge/common/errors.py +++ b/cforge/common/errors.py @@ -9,12 +9,15 @@ boundary between internal failures and surfaced command errors. """ +# Standard from enum import Enum -import json from typing import Any, Optional, Tuple +import json +# Third-Party import typer +# Local from cforge.common.console import get_console @@ -56,6 +59,7 @@ def split_exception_details(exception: Exception) -> Tuple[str, Any]: def handle_exception(exception: Exception) -> None: """Handle an exception and print a friendly error message.""" + # Local from cforge.common.render import print_json e_str, e_detail = split_exception_details(exception) diff --git a/cforge/common/http.py b/cforge/common/http.py index 0de7755..7e45ae2 100644 --- a/cforge/common/http.py +++ b/cforge/common/http.py @@ -9,11 +9,14 @@ helpers instead of handling auth headers and base URL resolution themselves. """ +# Standard from pathlib import Path from typing import Any, Dict, Optional +# Third-Party import requests +# Local from cforge.common.errors import AuthenticationError, CLIError from cforge.config import get_settings from cforge.credential_store import load_profile_credentials diff --git a/cforge/common/prompting.py b/cforge/common/prompting.py index 0e36455..3e329f3 100644 --- a/cforge/common/prompting.py +++ b/cforge/common/prompting.py @@ -10,13 +10,16 @@ commands that need structured request payloads. """ +# Standard +from typing import Annotated, Any, Callable, Dict, List, Optional, Tuple, Union, get_args, get_origin, get_type_hints import json -from typing import Annotated, Any, Callable, Dict, get_args, get_origin, get_type_hints, List, Optional, Tuple, Union +# Third-Party from pydantic import BaseModel from rich.console import Console import typer +# Local from cforge.common.console import get_console from cforge.common.errors import CLIError from cforge.common.schema_validation import validate_instance, validate_instance_against_subschema, validate_schema diff --git a/cforge/common/render.py b/cforge/common/render.py index c007a0d..203521a 100644 --- a/cforge/common/render.py +++ b/cforge/common/render.py @@ -9,16 +9,19 @@ data retrieval while sharing a consistent terminal presentation. """ -import json +# Standard from typing import Any, Dict, List, Optional +import json -from rich.console import Console, ConsoleOptions, RenderableType, RenderResult +# Third-Party +from rich.console import Console, ConsoleOptions, RenderResult, RenderableType from rich.measure import Measurement from rich.panel import Panel from rich.segment import Segment from rich.syntax import Syntax from rich.table import Table +# Local from cforge.common.console import get_console from cforge.config import get_settings diff --git a/cforge/credential_store.py b/cforge/credential_store.py index eca88f6..aa85d6b 100644 --- a/cforge/credential_store.py +++ b/cforge/credential_store.py @@ -8,9 +8,9 @@ """ # Standard -import json from pathlib import Path from typing import Optional +import json # Third-Party from cryptography.hazmat.backends import default_backend @@ -18,7 +18,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -# First-Party +# Local from cforge.config import get_settings diff --git a/cforge/main.py b/cforge/main.py index 2d75eb8..155a189 100644 --- a/cforge/main.py +++ b/cforge/main.py @@ -28,6 +28,7 @@ # Third-Party import typer +# Local from cforge.commands.deploy.deploy import deploy from cforge.commands.metrics.metrics import metrics_get, metrics_reset from cforge.commands.resources.a2a import ( @@ -47,11 +48,7 @@ mcp_servers_toggle, mcp_servers_update, ) -from cforge.commands.resources.plugins import ( - plugins_get, - plugins_list, - plugins_stats, -) +from cforge.commands.resources.plugins import plugins_get, plugins_list, plugins_stats from cforge.commands.resources.prompts import ( prompts_create, prompts_delete, @@ -101,8 +98,6 @@ from cforge.commands.settings.support_bundle import support_bundle from cforge.commands.settings.version import version from cforge.commands.settings.whoami import whoami - -# First-Party from cforge.common.console import get_app # Get the main app singleton diff --git a/pyproject.toml b/pyproject.toml index dc0c328..e9a54b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -171,8 +171,8 @@ target-version = "py311" [tool.ruff.lint] # Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default. -# Also "D1" for docstring present checks. -# TODO: Enable "I" for import sorting as a separate PR. +# Also "D1" for docstring present checks +# "I" for import sorting. select = ["E3", "E4", "E7", "E9", "F", "D1"] ignore = [] @@ -222,16 +222,16 @@ from-first = true # place all "from ... import ..." befo ############################################################################### # What belongs where ############################################################################### -known-first-party = ["cforge", "mcpgateway"] # treat "cforge" and "mcpgateway" as FIRSTPARTY -known-local-folder = ["tests", "scripts"] # treat these folders as LOCALFOLDER +known-first-party = ["mcpgateway"] # treat "mcpgateway" as FIRSTPARTY +known-local-folder = ["tests", "scripts","cforge"] # treat these folders as LOCALFOLDER known-third-party = ["alembic"] # treat "alembic" as THIRDPARTY # src-paths = ["src/cforge"] # uncomment only if package moves under src/ ############################################################################### # Style niceties ############################################################################### -force-sort-within-sections = true # always alphabetise names inside each block -order-by-type = false # don't group imports by "type vs. straight name" +# force_alphabetical_sort_within_sections = true # always alphabetise names inside each block +# order-by-type = true # don't group imports by "type vs. straight name" # balanced-wrapping = true # spread wrapped imports evenly between lines # lines-between-sections = 1 # exactly one blank line between the five groups # lines-between-types = 1 # one blank line between 'import X' and 'from X import ...' @@ -260,10 +260,10 @@ disallow_untyped_defs = false # Core behaviour ############################################################################### profile = "black" # inherit Black's own import-sorting profile +from_first = true # place all "from ... import ..." before plain "import ..." line_length = 200 # match Black's custom line length multi_line_output = 3 # vertical-hanging-indent style include_trailing_comma = true # keep trailing commas for Black -from_first = true # place all "from ... import ..." before plain "import ..." ############################################################################### # Section ordering & headings @@ -278,18 +278,17 @@ import_heading_localfolder = "Local" # header for ad-hoc scripts / tests ############################################################################### # What belongs where ############################################################################### -known_first_party = ["cforge", "mcpgateway"] # treat "mcpgateway.*" as FIRSTPARTY -known_local_folder = ["tests", "scripts"] # treat these folders as LOCALFOLDER +known_first_party = ["mcpgateway"] # treat "mcpgateway" as FIRSTPARTY +known_local_folder = ["tests", "scripts", "cforge"] # treat these folders as LOCALFOLDER # src_paths = ["src/cforge"] # uncomment only if package moves under src/ ############################################################################### # Style niceties ############################################################################### -force_sort_within_sections = true # always alphabetise names inside each block +case_sensitive = true # case sensitive sorting order_by_type = false # don't group imports by "type vs. straight name" balanced_wrapping = true # spread wrapped imports evenly between lines lines_between_sections = 1 # exactly one blank line between the five groups -lines_between_types = 1 # one blank line between 'import X' and 'from X import ...' no_lines_before = ["LOCALFOLDER"] # suppress blank line *before* the LOCALFOLDER block ensure_newline_before_comments = true # newline before any inline # comment after an import diff --git a/tests/commands/deploy/test_deploy.py b/tests/commands/deploy/test_deploy.py index ff57d00..2adf8f4 100644 --- a/tests/commands/deploy/test_deploy.py +++ b/tests/commands/deploy/test_deploy.py @@ -14,7 +14,7 @@ import pytest import typer -# First-Party +# Local from cforge.commands.deploy.deploy import deploy diff --git a/tests/commands/metrics/test_metrics.py b/tests/commands/metrics/test_metrics.py index 6b19cc2..87446cd 100644 --- a/tests/commands/metrics/test_metrics.py +++ b/tests/commands/metrics/test_metrics.py @@ -14,7 +14,7 @@ import pytest import typer -# First-Party +# Local from cforge.commands.metrics.metrics import metrics_get, metrics_reset diff --git a/tests/commands/resources/test_a2a.py b/tests/commands/resources/test_a2a.py index 7f47a69..7eb238a 100644 --- a/tests/commands/resources/test_a2a.py +++ b/tests/commands/resources/test_a2a.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.a2a import ( a2a_create, a2a_delete, diff --git a/tests/commands/resources/test_mcp_servers.py b/tests/commands/resources/test_mcp_servers.py index e3e83c2..53ca900 100644 --- a/tests/commands/resources/test_mcp_servers.py +++ b/tests/commands/resources/test_mcp_servers.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.mcp_servers import ( mcp_servers_create, mcp_servers_delete, diff --git a/tests/commands/resources/test_plugins.py b/tests/commands/resources/test_plugins.py index ad0af73..9bd7a54 100644 --- a/tests/commands/resources/test_plugins.py +++ b/tests/commands/resources/test_plugins.py @@ -11,8 +11,8 @@ import pytest import typer -# First-Party -from cforge.commands.resources.plugins import _parse_plugin_mode, PluginMode, plugins_get, plugins_list, plugins_stats +# Local +from cforge.commands.resources.plugins import PluginMode, _parse_plugin_mode, plugins_get, plugins_list, plugins_stats from cforge.common.errors import AuthenticationError, CLIError from cforge.main import app from tests.conftest import invoke_typer_command, patch_functions diff --git a/tests/commands/resources/test_prompts.py b/tests/commands/resources/test_prompts.py index 87ef17e..958c400 100644 --- a/tests/commands/resources/test_prompts.py +++ b/tests/commands/resources/test_prompts.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.prompts import ( prompts_create, prompts_delete, diff --git a/tests/commands/resources/test_resources.py b/tests/commands/resources/test_resources.py index 68e5f54..0d8c54c 100644 --- a/tests/commands/resources/test_resources.py +++ b/tests/commands/resources/test_resources.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.resources import ( resources_create, resources_delete, diff --git a/tests/commands/resources/test_tools.py b/tests/commands/resources/test_tools.py index 7a6c3c8..32c6a49 100644 --- a/tests/commands/resources/test_tools.py +++ b/tests/commands/resources/test_tools.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.tools import ( tools_create, tools_delete, diff --git a/tests/commands/resources/test_virtual_servers.py b/tests/commands/resources/test_virtual_servers.py index f6b2b7f..639f879 100644 --- a/tests/commands/resources/test_virtual_servers.py +++ b/tests/commands/resources/test_virtual_servers.py @@ -8,17 +8,17 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import MagicMock, patch +import json +import tempfile # Third-Party import click import pytest import typer -# First-Party +# Local from cforge.commands.resources.prompts import prompts_list from cforge.commands.resources.resources import resources_list from cforge.commands.resources.tools import tools_list diff --git a/tests/commands/server/test_run.py b/tests/commands/server/test_run.py index 815fc7b..bfacc6f 100644 --- a/tests/commands/server/test_run.py +++ b/tests/commands/server/test_run.py @@ -10,7 +10,7 @@ # Standard from unittest.mock import MagicMock, patch -# First-Party +# Local from cforge.commands.server.run import run from tests.conftest import invoke_typer_command @@ -502,6 +502,7 @@ def test_run_register_without_source_warns(self) -> None: def test_run_health_check_connection_error_retry(self) -> None: """Test that health check retries on connection errors.""" + # Third-Party import requests as real_requests with ( @@ -542,8 +543,8 @@ def test_run_health_check_connection_error_retry(self) -> None: def test_run_health_check_timeout(self) -> None: """Test that health check timeout exits with error.""" + # Third-Party import requests as real_requests - import typer with ( diff --git a/tests/commands/server/test_serve.py b/tests/commands/server/test_serve.py index 6eebda8..3bf1e27 100644 --- a/tests/commands/server/test_serve.py +++ b/tests/commands/server/test_serve.py @@ -15,7 +15,7 @@ # Third-Party import requests -# First-Party +# Local from cforge.commands.server.serve import serve from tests.conftest import get_open_port, invoke_typer_command diff --git a/tests/commands/settings/test_config_schema.py b/tests/commands/settings/test_config_schema.py index a38195f..1647576 100644 --- a/tests/commands/settings/test_config_schema.py +++ b/tests/commands/settings/test_config_schema.py @@ -8,12 +8,12 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile -# First-Party +# Local from cforge.commands.settings.config_schema import config_schema diff --git a/tests/commands/settings/test_export.py b/tests/commands/settings/test_export.py index 9429f1c..6873729 100644 --- a/tests/commands/settings/test_export.py +++ b/tests/commands/settings/test_export.py @@ -8,16 +8,16 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import pytest import typer -# First-Party +# Local from cforge.commands.settings.export import export @@ -49,6 +49,7 @@ def test_export_with_custom_output(self, mock_base_url, mock_console) -> None: def test_export_with_default_filename(self, mock_base_url, mock_console) -> None: """Test export with auto-generated filename.""" mock_export_data = {"metadata": {"entity_counts": {}}} + # Standard import os with tempfile.TemporaryDirectory() as temp_dir: diff --git a/tests/commands/settings/test_import_cmd.py b/tests/commands/settings/test_import_cmd.py index 4d1819b..ae4202f 100644 --- a/tests/commands/settings/test_import_cmd.py +++ b/tests/commands/settings/test_import_cmd.py @@ -8,16 +8,16 @@ """ # Standard -import json -import tempfile from pathlib import Path from unittest.mock import patch +import json +import tempfile # Third-Party import pytest import typer -# First-Party +# Local from cforge.commands.settings.import_cmd import import_cmd diff --git a/tests/commands/settings/test_login.py b/tests/commands/settings/test_login.py index 1aa83cb..7d1ede8 100644 --- a/tests/commands/settings/test_login.py +++ b/tests/commands/settings/test_login.py @@ -9,15 +9,15 @@ # Standard from pathlib import Path -import tempfile from unittest.mock import Mock, patch +import tempfile # Third-Party import pytest import requests import typer -# First-Party +# Local from cforge.commands.settings.login import login from cforge.common.errors import AuthenticationError from cforge.common.http import make_authenticated_request @@ -144,8 +144,10 @@ class TestLoginWithProfiles: def test_login_saves_to_profile_specific_token_file(self, mock_base_url, mock_console, mock_settings) -> None: """Test that login saves token to profile-specific file when profile is active.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store mock_response = Mock() @@ -181,8 +183,10 @@ def test_login_saves_to_profile_specific_token_file(self, mock_base_url, mock_co def test_login_with_multiple_profiles(self, mock_base_url, mock_console, mock_settings) -> None: """Test that different profiles can have different tokens.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store profile_id1 = "profile-1" diff --git a/tests/commands/settings/test_logout.py b/tests/commands/settings/test_logout.py index b38e860..9f766cd 100644 --- a/tests/commands/settings/test_logout.py +++ b/tests/commands/settings/test_logout.py @@ -9,13 +9,13 @@ # Standard from pathlib import Path -import tempfile from unittest.mock import patch +import tempfile # Third-Party import pytest -# First-Party +# Local from cforge.commands.settings.login import login from cforge.commands.settings.logout import logout from cforge.common.errors import AuthenticationError @@ -93,8 +93,10 @@ class TestLogoutWithProfiles: def test_logout_removes_profile_specific_token(self, mock_console, mock_settings) -> None: """Test that logout removes profile-specific token file.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store # Create and save an active profile @@ -130,8 +132,10 @@ def test_logout_removes_profile_specific_token(self, mock_console, mock_settings def test_logout_only_removes_active_profile_token(self, mock_console, mock_settings) -> None: """Test that logout only removes the active profile's token, not others.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store profile_id1 = "profile-1" diff --git a/tests/commands/settings/test_profiles.py b/tests/commands/settings/test_profiles.py index 9b5e5cc..90a7be0 100644 --- a/tests/commands/settings/test_profiles.py +++ b/tests/commands/settings/test_profiles.py @@ -18,7 +18,7 @@ import pytest import typer -# First-Party +# Local from cforge.commands.settings.profiles import ( profiles_create, profiles_get, @@ -27,11 +27,11 @@ ) from cforge.profile_utils import ( AuthProfile, + DEFAULT_PROFILE_ID, ProfileMetadata, ProfileStore, - save_profile_store, load_profile_store, - DEFAULT_PROFILE_ID, + save_profile_store, ) @@ -103,6 +103,7 @@ def test_profiles_list_empty(self, mock_console, mock_settings) -> None: def test_profiles_list_with_active_profile(self, mock_console, mock_settings) -> None: """Test listing profiles when there is an active profile.""" + # Standard from datetime import datetime # Create test profiles with one active @@ -140,6 +141,7 @@ def test_profiles_list_with_active_profile(self, mock_console, mock_settings) -> def test_profiles_list_without_active_profile(self, mock_console, mock_settings) -> None: """Test listing profiles when there is not an active profile.""" + # Standard from datetime import datetime # Create test profiles with one active @@ -623,6 +625,7 @@ def test_profiles_create_enable_fails(self, mock_console, mock_settings) -> None def test_profiles_create_with_existing_store(self, mock_console, mock_settings) -> None: """Test creating a profile when a profile store already exists.""" + # Local from cforge.profile_utils import load_profile_store # Create an existing profile store diff --git a/tests/commands/settings/test_support_bundle.py b/tests/commands/settings/test_support_bundle.py index c7432d8..e1efea3 100644 --- a/tests/commands/settings/test_support_bundle.py +++ b/tests/commands/settings/test_support_bundle.py @@ -8,15 +8,15 @@ """ # Standard -import tempfile from pathlib import Path from unittest.mock import Mock, patch +import tempfile # Third-Party import pytest import typer -# First-Party +# Local from cforge.commands.settings.support_bundle import support_bundle diff --git a/tests/commands/settings/test_version.py b/tests/commands/settings/test_version.py index 1464703..1c6a9a0 100644 --- a/tests/commands/settings/test_version.py +++ b/tests/commands/settings/test_version.py @@ -8,9 +8,11 @@ """ # First-Party -from cforge.commands.settings.version import version from mcpgateway import __version__ +# Local +from cforge.commands.settings.version import version + class TestVersionCommand: """Tests for version command.""" diff --git a/tests/commands/settings/test_whoami.py b/tests/commands/settings/test_whoami.py index b8cc20f..12eeb68 100644 --- a/tests/commands/settings/test_whoami.py +++ b/tests/commands/settings/test_whoami.py @@ -10,7 +10,7 @@ # Standard from unittest.mock import patch -# First-Party +# Local from cforge.commands.settings.whoami import whoami @@ -80,9 +80,12 @@ class TestWhoamiWithProfiles: def test_whoami_with_active_profile_and_token(self, mock_settings, mock_console) -> None: """Test whoami displays active profile information along with auth status.""" - from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store + # Standard from datetime import datetime + # Local + from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store + # Create and save an active profile profile_id = "test-profile-whoami" profile = AuthProfile( @@ -125,9 +128,12 @@ def test_whoami_with_active_profile_and_token(self, mock_settings, mock_console) def test_whoami_with_active_profile_with_metadata(self, mock_settings, mock_console) -> None: """Test whoami displays profile metadata when available.""" - from cforge.profile_utils import AuthProfile, ProfileMetadata, ProfileStore, save_profile_store + # Standard from datetime import datetime + # Local + from cforge.profile_utils import AuthProfile, ProfileMetadata, ProfileStore, save_profile_store + # Create profile with metadata profile_id = "test-profile-metadata" metadata = ProfileMetadata( @@ -165,9 +171,12 @@ def test_whoami_with_active_profile_with_metadata(self, mock_settings, mock_cons def test_whoami_with_active_profile_no_auth(self, mock_settings, mock_console) -> None: """Test whoami with active profile but no authentication.""" - from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store + # Standard from datetime import datetime + # Local + from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store + # Create and save an active profile profile_id = "test-profile-noauth" profile = AuthProfile( diff --git a/tests/common/test_console.py b/tests/common/test_console.py index eb5fe43..94b5c85 100644 --- a/tests/common/test_console.py +++ b/tests/common/test_console.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Tests for cforge.common.console.""" -# First-Party +# Local from cforge.common.console import get_app, get_console diff --git a/tests/common/test_errors.py b/tests/common/test_errors.py index 4884300..9bb5c87 100644 --- a/tests/common/test_errors.py +++ b/tests/common/test_errors.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Tests for cforge.common.errors.""" -# First-Party +# Local from cforge.common.errors import AuthenticationError, CLIError diff --git a/tests/common/test_http.py b/tests/common/test_http.py index 41c4e91..a3d74e1 100644 --- a/tests/common/test_http.py +++ b/tests/common/test_http.py @@ -3,15 +3,15 @@ # Standard from pathlib import Path +from unittest.mock import Mock, patch import stat import tempfile -from unittest.mock import Mock, patch # Third-Party import pytest import requests -# First-Party +# Local from cforge.common.errors import AuthenticationError, CLIError from cforge.common.http import get_auth_token, get_token_file, load_token, make_authenticated_request, save_token from tests.conftest import mock_client_login @@ -29,8 +29,10 @@ def test_get_token_file(self, mock_settings) -> None: def test_get_token_file_with_active_profile(self, mock_settings) -> None: """Test getting the token file path uses active profile when available.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store # Create and save an active profile @@ -66,8 +68,10 @@ def test_save_and_load_token(self) -> None: def test_save_and_load_token_with_active_profile(self, mock_settings) -> None: """Test saving and loading a token with an active profile.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store test_token = "profile_token_456" @@ -100,8 +104,10 @@ def test_save_and_load_token_with_active_profile(self, mock_settings) -> None: def test_save_token_different_profiles(self, mock_settings) -> None: """Test that different profiles have separate token files.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store token1 = "token_for_profile_1" @@ -162,8 +168,10 @@ def test_load_token_nonexistent(self, tmp_path: Path) -> None: def test_load_token_nonexistent_profile(self, mock_settings) -> None: """Test loading a token for a profile that doesn't have a token file.""" + # Standard from datetime import datetime + # Local from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store profile_id = "nonexistent-profile" @@ -194,8 +202,10 @@ class TestBaseUrl: def test_get_base_url_with_active_profile(self, mock_settings) -> None: """Test get_base_url returns profile's API URL when active profile exists.""" + # Standard from datetime import datetime + # Local from cforge.common.http import get_base_url from cforge.profile_utils import AuthProfile, ProfileStore, save_profile_store @@ -220,6 +230,7 @@ def test_get_base_url_with_active_profile(self, mock_settings) -> None: def test_get_base_url_without_active_profile(self, mock_settings) -> None: """Test get_base_url returns default URL when no active profile.""" + # Local from cforge.common.http import get_base_url # No profile saved, should use settings @@ -261,6 +272,7 @@ class TestAutoLogin: def test_attempt_auto_login_no_profile(self, mock_settings): """Test auto-login when no profile is active.""" + # Local from cforge.common.http import attempt_auto_login token = attempt_auto_login() @@ -268,8 +280,10 @@ def test_attempt_auto_login_no_profile(self, mock_settings): def test_attempt_auto_login_no_credentials(self, mock_settings): """Test auto-login when credentials are not available.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -289,8 +303,10 @@ def test_attempt_auto_login_no_credentials(self, mock_settings): def test_attempt_auto_login_missing_email(self, mock_settings): """Test auto-login when email is missing from credentials.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -310,8 +326,10 @@ def test_attempt_auto_login_missing_email(self, mock_settings): def test_attempt_auto_login_missing_password(self, mock_settings): """Test auto-login when password is missing from credentials.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -332,8 +350,10 @@ def test_attempt_auto_login_missing_password(self, mock_settings): @patch("cforge.common.http.requests.post") def test_attempt_auto_login_success(self, mock_post, mock_settings): """Test successful auto-login.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login, load_token from cforge.profile_utils import AuthProfile @@ -364,8 +384,10 @@ def test_attempt_auto_login_success(self, mock_post, mock_settings): @patch("cforge.common.http.requests.post") def test_attempt_auto_login_failed_login(self, mock_post, mock_settings): """Test auto-login when login fails.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -391,8 +413,10 @@ def test_attempt_auto_login_failed_login(self, mock_post, mock_settings): @patch("cforge.common.http.requests.post") def test_attempt_auto_login_no_token_in_response(self, mock_post, mock_settings): """Test auto-login when response doesn't contain token.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -419,8 +443,10 @@ def test_attempt_auto_login_no_token_in_response(self, mock_post, mock_settings) @patch("cforge.common.http.requests.post") def test_attempt_auto_login_request_exception(self, mock_post, mock_settings): """Test auto-login when request raises exception.""" + # Standard from datetime import datetime + # Local from cforge.common.http import attempt_auto_login from cforge.profile_utils import AuthProfile @@ -443,6 +469,7 @@ def test_attempt_auto_login_request_exception(self, mock_post, mock_settings): def test_get_auth_token_with_auto_login(self, mock_settings): """Test that get_auth_token attempts auto-login when no token is available.""" + # Local from cforge.common.http import get_auth_token # Mock no env token and no file token, but successful auto-login diff --git a/tests/common/test_prompting.py b/tests/common/test_prompting.py index 216fdb8..350c227 100644 --- a/tests/common/test_prompting.py +++ b/tests/common/test_prompting.py @@ -10,19 +10,19 @@ from pydantic import BaseModel, Field import pytest -# First-Party +# Local from cforge.common.errors import CLIError from cforge.common.prompting import ( + _INT_SENTINEL_DEFAULT, _build_prompt_text, _infer_schema_type, - _INT_SENTINEL_DEFAULT, - prompt_for_json_schema, - prompt_for_schema, _resolve_effective_schema, _resolve_ref_schema, _resolve_schema_type, _schema_contains_ref, _strip_schema_internal_properties, + prompt_for_json_schema, + prompt_for_schema, ) diff --git a/tests/common/test_render.py b/tests/common/test_render.py index 44b1133..d1a89d7 100644 --- a/tests/common/test_render.py +++ b/tests/common/test_render.py @@ -6,7 +6,7 @@ from rich.syntax import Syntax from rich.table import Table -# First-Party +# Local from cforge.common.render import LineLimit, print_json, print_table @@ -15,6 +15,7 @@ class TestLineLimit: def test_line_limit_basic_truncation(self) -> None: """Test that LineLimit truncates content to max_lines.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -40,6 +41,7 @@ def test_line_limit_basic_truncation(self) -> None: def test_line_limit_no_truncation_needed(self) -> None: """Test that LineLimit doesn't truncate when content is within limit.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -60,6 +62,7 @@ def test_line_limit_no_truncation_needed(self) -> None: def test_line_limit_exact_match(self) -> None: """Test LineLimit when content exactly matches max_lines.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -81,6 +84,7 @@ def test_line_limit_exact_match(self) -> None: def test_line_limit_zero_lines(self) -> None: """Test LineLimit with max_lines=0 shows only ellipsis.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -99,6 +103,7 @@ def test_line_limit_zero_lines(self) -> None: def test_line_limit_one_line(self) -> None: """Test LineLimit with max_lines=1.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -118,6 +123,7 @@ def test_line_limit_one_line(self) -> None: def test_line_limit_with_long_single_line(self) -> None: """Test LineLimit with a single long line that wraps.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -138,6 +144,7 @@ def test_line_limit_with_long_single_line(self) -> None: def test_line_limit_measurement_passthrough(self) -> None: """Test that LineLimit passes through measurement to wrapped renderable.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -155,6 +162,7 @@ def test_line_limit_measurement_passthrough(self) -> None: def test_line_limit_with_empty_content(self) -> None: """Test LineLimit with empty content.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -172,6 +180,7 @@ def test_line_limit_with_empty_content(self) -> None: def test_line_limit_preserves_styling(self) -> None: """Test that LineLimit preserves rich styling in truncated content.""" + # Third-Party from rich.console import Console from rich.text import Text @@ -258,6 +267,7 @@ def test_print_table_missing_columns(self, mock_console) -> None: def test_print_table_wraps_all_cells_with_line_limit(self) -> None: """Test that print_table wraps all cell values with LineLimit for truncation.""" + # Standard from unittest.mock import patch # Create test data with various types @@ -284,6 +294,7 @@ def test_print_table_wraps_all_cells_with_line_limit(self) -> None: def test_print_table_with_custom_max_lines(self, mock_settings) -> None: """Test that print_table respects custom table_max_lines configuration.""" + # Standard from unittest.mock import patch # Configure mock_settings with custom max_lines value @@ -312,6 +323,7 @@ def test_print_table_with_custom_max_lines(self, mock_settings) -> None: def test_print_table_with_disabled_line_limit(self, mock_settings) -> None: """Test that print_table skips LineLimit wrapping when table_max_lines is 0 or negative.""" + # Standard from unittest.mock import patch # Configure mock_settings with disabled max_lines value (0) diff --git a/tests/common/test_schema_validation.py b/tests/common/test_schema_validation.py index 4d68e3a..5d1f9ba 100644 --- a/tests/common/test_schema_validation.py +++ b/tests/common/test_schema_validation.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """Tests for cforge.common.schema_validation.""" -# First-Party +# Local from cforge.common.schema_validation import validate_instance, validate_instance_against_subschema, validate_schema diff --git a/tests/conftest.py b/tests/conftest.py index 3c21f61..43b36f9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,27 +9,26 @@ # Standard from contextlib import contextmanager +from pathlib import Path +from types import SimpleNamespace +from typing import Any, Callable, Generator, List, Union +from unittest.mock import Mock, patch import inspect import logging import os -from pathlib import Path import socket import sys import tempfile import threading import time -from types import SimpleNamespace -from typing import Any, Callable, Generator, List, Union -from unittest.mock import Mock, patch +# Third-Party from fastapi.testclient import TestClient from mcp.server.fastmcp import FastMCP from pydantic import SecretStr - -# Third-Party -import pytest from typer.models import OptionInfo from typer.testing import CliRunner +import pytest import urllib3 import uvicorn @@ -39,7 +38,7 @@ os.environ["DATABASE_URL"] = f"sqlite:////{working_dir.__enter__()}/mcp.db" -# First-Party +# Local from cforge.config import CLISettings, get_settings # noqa: E402 # Suppress urllib3 retry warnings during tests @@ -263,6 +262,7 @@ def test_endpoint(mock_client): response = mock_client.get("/health") assert response.status_code == 200 """ + # First-Party from mcpgateway.main import app client = TestClient(app) diff --git a/tests/test_config.py b/tests/test_config.py index 6ac9f39..44b2d41 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -13,7 +13,7 @@ import os import tempfile -# First-Party +# Local from cforge.config import get_settings diff --git a/tests/test_credential_store.py b/tests/test_credential_store.py index a44998d..df4dae4 100644 --- a/tests/test_credential_store.py +++ b/tests/test_credential_store.py @@ -16,7 +16,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC -# First-Party +# Local from cforge.credential_store import ( decrypt_credential_data, get_credential_store_path, @@ -73,6 +73,7 @@ class TestCredentialDecryption: def _encrypt_data(self, data: str, encryption_key: str) -> bytes: """Helper to encrypt data using the same format as electron-store/conf.""" + # Standard import os # Generate random IV @@ -226,6 +227,7 @@ def test_load_profile_credentials_decryption_fails(self, mock_settings): def _encrypt_data(self, data: str, encryption_key: str) -> bytes: """Helper to encrypt data using the same format as electron-store/conf.""" + # Standard import os # Generate random IV diff --git a/tests/test_main.py b/tests/test_main.py index 1b47eea..b289f48 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -10,7 +10,7 @@ # Third-Party from typer.testing import CliRunner -# First-Party +# Local from cforge.main import app diff --git a/tests/test_profile_utils.py b/tests/test_profile_utils.py index 95e5d63..749a3cf 100644 --- a/tests/test_profile_utils.py +++ b/tests/test_profile_utils.py @@ -8,18 +8,18 @@ """ # Standard -import json from datetime import datetime from pathlib import Path +import json -# First-Party +# Local from cforge.profile_utils import ( AuthProfile, DEFAULT_PROFILE_ID, ProfileMetadata, ProfileStore, - get_all_profiles, get_active_profile, + get_all_profiles, get_profile, get_profile_store_path, load_profile_store, @@ -376,6 +376,7 @@ def test_save_profile_store_creates_directory(self, mock_settings) -> None: # Ensure directory doesn't exist if store_path.parent.exists(): + # Standard import shutil shutil.rmtree(store_path.parent)