diff --git a/.github/instructions/style-guide.instructions.md b/.github/instructions/style-guide.instructions.md index edee2e0b59..643a7c0662 100644 --- a/.github/instructions/style-guide.instructions.md +++ b/.github/instructions/style-guide.instructions.md @@ -192,6 +192,22 @@ from pyrit.common.net_utility import get_httpx_client Within the same package, import from the specific file to avoid circular imports. +### Typing Backports (`typing_extensions`) + +For typing features that don't exist on every supported Python (`Self`, +`override`, `TypeAlias`, `Unpack`, `NotRequired`, etc.), import from +``typing_extensions`` rather than ``typing``. `typing_extensions` is already a +transitive dependency (pulled in by ``pydantic``) and works across all supported +Python versions, so this avoids per-version branching and ``# type: ignore`` noise. + +```python +# CORRECT — works on 3.10+ +from typing_extensions import Self, override + +# INCORRECT — `Self` is 3.11+, `override` is 3.12+, breaks on older runtimes +from typing import Self, override +``` + ## Documentation Standards ### Docstring Format diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index dfc1e5f889..41c9290169 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -80,11 +80,16 @@ repos: name: Ruff (Jupyter Notebooks) args: [--fix] - - repo: https://github.com/allganize/ty-pre-commit - rev: v0.0.43 + - repo: local hooks: - id: ty-check name: ty (type check) + # Run ty in the project's uv-managed venv so it can resolve project + # dependencies (tqdm etc). The third-party ty-pre-commit hook installs + # ty in an isolated env without project deps, which causes spurious + # ``unresolved-import`` errors now that the rule is set to ``error``. + entry: uv run --link-mode=copy ty check + language: system files: ^pyrit/ exclude: ^pyrit/auxiliary_attacks/ types: [python] diff --git a/doc/code/scenarios/1_common_scenario_parameters.ipynb b/doc/code/scenarios/1_common_scenario_parameters.ipynb index c3b1ce4960..2cd6692b27 100644 --- a/doc/code/scenarios/1_common_scenario_parameters.ipynb +++ b/doc/code/scenarios/1_common_scenario_parameters.ipynb @@ -80,7 +80,7 @@ "\n", "from pyrit.output import output_scenario_async\n", "from pyrit.registry import TargetRegistry\n", - "from pyrit.scenario.scenarios.foundry import FoundryStrategy, RedTeamAgent\n", + "from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent\n", "from pyrit.setup import initialize_from_config_async\n", "\n", "await initialize_from_config_async(config_path=Path(\"../../scanner/pyrit_conf.yaml\")) # type: ignore\n", @@ -204,7 +204,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pyrit.scenario.scenarios.foundry import FoundryComposite\n", + "from pyrit.scenario.foundry import FoundryComposite\n", "\n", "composite_strategy = [FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Base64])]" ] diff --git a/doc/code/scenarios/1_common_scenario_parameters.py b/doc/code/scenarios/1_common_scenario_parameters.py index 0d20238d44..28d83a6527 100644 --- a/doc/code/scenarios/1_common_scenario_parameters.py +++ b/doc/code/scenarios/1_common_scenario_parameters.py @@ -30,7 +30,7 @@ from pyrit.output import output_scenario_async from pyrit.registry import TargetRegistry -from pyrit.scenario.scenarios.foundry import FoundryStrategy, RedTeamAgent +from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent from pyrit.setup import initialize_from_config_async await initialize_from_config_async(config_path=Path("../../scanner/pyrit_conf.yaml")) # type: ignore @@ -85,7 +85,7 @@ # For example, to run Crescendo with Base64 encoding applied: # %% -from pyrit.scenario.scenarios.foundry import FoundryComposite +from pyrit.scenario.foundry import FoundryComposite composite_strategy = [FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Base64])] diff --git a/doc/code/scenarios/2_custom_scenario_parameters.ipynb b/doc/code/scenarios/2_custom_scenario_parameters.ipynb index e1c623b79a..c1c2e5667f 100644 --- a/doc/code/scenarios/2_custom_scenario_parameters.ipynb +++ b/doc/code/scenarios/2_custom_scenario_parameters.ipynb @@ -68,7 +68,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt.scam import Scam\n", + "from pyrit.scenario.airt.scam import Scam\n", "from pyrit.setup import initialize_pyrit_async\n", "from pyrit.setup.initializers.components import ScenarioTechniqueInitializer\n", "\n", diff --git a/doc/code/scenarios/2_custom_scenario_parameters.py b/doc/code/scenarios/2_custom_scenario_parameters.py index 4222fff9a9..946ca2d5f5 100644 --- a/doc/code/scenarios/2_custom_scenario_parameters.py +++ b/doc/code/scenarios/2_custom_scenario_parameters.py @@ -47,8 +47,7 @@ # would wire up memory and scorers): # %% - -from pyrit.scenario.scenarios.airt.scam import Scam +from pyrit.scenario.airt.scam import Scam from pyrit.setup import initialize_pyrit_async from pyrit.setup.initializers.components import ScenarioTechniqueInitializer diff --git a/doc/scanner/airt.ipynb b/doc/scanner/airt.ipynb index ba62a19214..e1881ef631 100644 --- a/doc/scanner/airt.ipynb +++ b/doc/scanner/airt.ipynb @@ -104,7 +104,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt import RapidResponse, RapidResponseStrategy\n", + "from pyrit.scenario.airt import RapidResponse, RapidResponseStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_hate\"], max_dataset_size=1)\n", "\n", @@ -254,7 +254,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt import Psychosocial, PsychosocialStrategy\n", + "from pyrit.scenario.airt import Psychosocial, PsychosocialStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_imminent_crisis\"], max_dataset_size=1)\n", "\n", @@ -398,7 +398,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt import Cyber, CyberStrategy\n", + "from pyrit.scenario.airt import Cyber, CyberStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_malware\"], max_dataset_size=1)\n", "\n", @@ -520,7 +520,7 @@ "metadata": {}, "outputs": [], "source": [ - "from pyrit.scenario.scenarios.airt import Jailbreak, JailbreakStrategy\n", + "from pyrit.scenario.airt import Jailbreak, JailbreakStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_harms\"], max_dataset_size=1)\n", "\n", @@ -1017,7 +1017,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt import Leakage, LeakageStrategy\n", + "from pyrit.scenario.airt import Leakage, LeakageStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_leakage\"], max_dataset_size=1)\n", "\n", @@ -1156,7 +1156,7 @@ } ], "source": [ - "from pyrit.scenario.scenarios.airt import Scam, ScamStrategy\n", + "from pyrit.scenario.airt import Scam, ScamStrategy\n", "\n", "dataset_config = DatasetConfiguration(dataset_names=[\"airt_scams\"], max_dataset_size=1)\n", "\n", diff --git a/doc/scanner/airt.py b/doc/scanner/airt.py index f31468972d..05312e7b42 100644 --- a/doc/scanner/airt.py +++ b/doc/scanner/airt.py @@ -50,7 +50,7 @@ # **Available strategies:** ALL, DEFAULT, SINGLE_TURN, MULTI_TURN, role_play, many_shot, tap # %% -from pyrit.scenario.scenarios.airt import RapidResponse, RapidResponseStrategy +from pyrit.scenario.airt import RapidResponse, RapidResponseStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_hate"], max_dataset_size=1) @@ -99,7 +99,7 @@ # meaningful because psychosocial harms emerge through multi-turn escalation. # %% -from pyrit.scenario.scenarios.airt import Psychosocial, PsychosocialStrategy +from pyrit.scenario.airt import Psychosocial, PsychosocialStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_imminent_crisis"], max_dataset_size=1) @@ -132,7 +132,7 @@ # **Available strategies:** ALL, MULTI_TURN, red_teaming # %% -from pyrit.scenario.scenarios.airt import Cyber, CyberStrategy +from pyrit.scenario.airt import Cyber, CyberStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_malware"], max_dataset_size=1) @@ -165,7 +165,7 @@ # **Available strategies:** ALL, SIMPLE, COMPLEX, PromptSending, ManyShot, SkeletonKey, RolePlay # %% -from pyrit.scenario.scenarios.airt import Jailbreak, JailbreakStrategy +from pyrit.scenario.airt import Jailbreak, JailbreakStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_harms"], max_dataset_size=1) @@ -213,7 +213,7 @@ # no built-in threshold — the scorer returns a raw float for you to interpret per your use case. # %% -from pyrit.scenario.scenarios.airt import Leakage, LeakageStrategy +from pyrit.scenario.airt import Leakage, LeakageStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_leakage"], max_dataset_size=1) @@ -245,7 +245,7 @@ # **Available strategies:** ALL, SINGLE_TURN, MULTI_TURN, ContextCompliance, RolePlay, PersuasiveRedTeamingAttack # %% -from pyrit.scenario.scenarios.airt import Scam, ScamStrategy +from pyrit.scenario.airt import Scam, ScamStrategy dataset_config = DatasetConfiguration(dataset_names=["airt_scams"], max_dataset_size=1) diff --git a/doc/scanner/benchmark.ipynb b/doc/scanner/benchmark.ipynb index 39b4f2f8a0..120ed75d65 100644 --- a/doc/scanner/benchmark.ipynb +++ b/doc/scanner/benchmark.ipynb @@ -63,7 +63,7 @@ "from pyrit.output import output_scenario_async\n", "from pyrit.prompt_target import OpenAIChatTarget\n", "from pyrit.scenario import DatasetConfiguration\n", - "from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark\n", + "from pyrit.scenario.benchmark import AdversarialBenchmark\n", "from pyrit.setup import IN_MEMORY, initialize_pyrit_async\n", "from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer\n", "\n", diff --git a/doc/scanner/benchmark.py b/doc/scanner/benchmark.py index 6dd097be10..f558ebaa08 100644 --- a/doc/scanner/benchmark.py +++ b/doc/scanner/benchmark.py @@ -50,7 +50,7 @@ from pyrit.output import output_scenario_async from pyrit.prompt_target import OpenAIChatTarget from pyrit.scenario import DatasetConfiguration -from pyrit.scenario.scenarios.benchmark import AdversarialBenchmark +from pyrit.scenario.benchmark import AdversarialBenchmark from pyrit.setup import IN_MEMORY, initialize_pyrit_async from pyrit.setup.initializers import LoadDefaultDatasets, ScorerInitializer, TargetInitializer diff --git a/doc/scanner/foundry.ipynb b/doc/scanner/foundry.ipynb index ec47624f47..9035823c9e 100644 --- a/doc/scanner/foundry.ipynb +++ b/doc/scanner/foundry.ipynb @@ -45,7 +45,7 @@ "from pyrit.output import output_scenario_async\n", "from pyrit.registry import TargetRegistry\n", "from pyrit.scenario import DatasetConfiguration\n", - "from pyrit.scenario.scenarios.foundry import FoundryStrategy, RedTeamAgent\n", + "from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent\n", "from pyrit.setup import initialize_from_config_async\n", "\n", "await initialize_from_config_async(config_path=Path(\"pyrit_conf.yaml\")) # type: ignore\n", @@ -216,7 +216,7 @@ "Each converter in the composite is applied in sequence before the attack runs.\n", "\n", "```python\n", - "from pyrit.scenario.scenarios.foundry import FoundryComposite\n", + "from pyrit.scenario.foundry import FoundryComposite\n", "\n", "composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n", "```" @@ -229,7 +229,7 @@ "metadata": {}, "outputs": [], "source": [ - "# from pyrit.scenario.scenarios.foundry import FoundryComposite\n", + "# from pyrit.scenario.foundry import FoundryComposite\n", "# composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap])\n", "# scenario_strategies = [FoundryStrategy.Base64, composed]" ] diff --git a/doc/scanner/foundry.py b/doc/scanner/foundry.py index f589614050..d4f9764c3a 100644 --- a/doc/scanner/foundry.py +++ b/doc/scanner/foundry.py @@ -25,7 +25,7 @@ from pyrit.output import output_scenario_async from pyrit.registry import TargetRegistry from pyrit.scenario import DatasetConfiguration -from pyrit.scenario.scenarios.foundry import FoundryStrategy, RedTeamAgent +from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent from pyrit.setup import initialize_from_config_async await initialize_from_config_async(config_path=Path("pyrit_conf.yaml")) # type: ignore @@ -77,13 +77,13 @@ # Each converter in the composite is applied in sequence before the attack runs. # # ```python -# from pyrit.scenario.scenarios.foundry import FoundryComposite +# from pyrit.scenario.foundry import FoundryComposite # # composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap]) # ``` # %% -# from pyrit.scenario.scenarios.foundry import FoundryComposite +# from pyrit.scenario.foundry import FoundryComposite # composed = FoundryComposite(attack=FoundryStrategy.Crescendo, converters=[FoundryStrategy.Caesar, FoundryStrategy.CharSwap]) # scenario_strategies = [FoundryStrategy.Base64, composed] diff --git a/doc/scanner/garak.ipynb b/doc/scanner/garak.ipynb index 592b0f5f72..6b9d68b91b 100644 --- a/doc/scanner/garak.ipynb +++ b/doc/scanner/garak.ipynb @@ -43,8 +43,8 @@ "\n", "from pyrit.output import output_scenario_async\n", "from pyrit.registry import TargetRegistry\n", - "from pyrit.scenario.scenarios.garak import Encoding, EncodingStrategy\n", - "from pyrit.scenario.scenarios.garak.encoding import EncodingDatasetConfiguration\n", + "from pyrit.scenario.garak import Encoding, EncodingStrategy\n", + "from pyrit.scenario.garak.encoding import EncodingDatasetConfiguration\n", "from pyrit.setup import initialize_from_config_async\n", "\n", "await initialize_from_config_async(config_path=Path(\"pyrit_conf.yaml\")) # type: ignore\n", diff --git a/doc/scanner/garak.py b/doc/scanner/garak.py index 7c3ea540a0..e86c03146f 100644 --- a/doc/scanner/garak.py +++ b/doc/scanner/garak.py @@ -23,8 +23,8 @@ from pyrit.output import output_scenario_async from pyrit.registry import TargetRegistry -from pyrit.scenario.scenarios.garak import Encoding, EncodingStrategy -from pyrit.scenario.scenarios.garak.encoding import EncodingDatasetConfiguration +from pyrit.scenario.garak import Encoding, EncodingStrategy +from pyrit.scenario.garak.encoding import EncodingDatasetConfiguration from pyrit.setup import initialize_from_config_async await initialize_from_config_async(config_path=Path("pyrit_conf.yaml")) # type: ignore diff --git a/pyproject.toml b/pyproject.toml index ee6ef1b728..00095ae93c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -82,7 +82,6 @@ dev = [ "jupytext>=1.17.1", "matplotlib>=3.10.0", "ty>=0.0.32", - "mock-alchemy>=0.2.6", "pandas>=2.2.0", "pre-commit>=4.2.0", "pytest>=9.0.3", @@ -94,17 +93,8 @@ dev = [ "respx>=0.22.0", "ruff>=0.14.4", "types-aiofiles>=24.1.0", - "types-cachetools>=5.5.0", - "types-decorator>=5.1.0", - "types-paramiko>=3.5.0", - "types-pycurl>=7.45.0", - "types-pytz>=2024.2.0", "types-PyYAML>=6.0.12.20250516", "types-requests>=2.31.0.20250515", - "types-simplejson>=3.19.0", - "types-six>=1.16.0", - "types-tabulate>=0.9.0", - "types-ujson>=5.10.0", ] [project.optional-dependencies] @@ -170,8 +160,6 @@ asyncio_mode = "auto" [tool.ty] [tool.ty.rules] all = "error" -# Suppress errors for missing third-party stubs -unresolved-import = "ignore" # Tolerate None attribute access possibly-missing-attribute = "ignore" # Allow existing type: ignore comments without matching errors diff --git a/pyrit/auth/copilot_authenticator.py b/pyrit/auth/copilot_authenticator.py index a069af33ff..2dda693e1f 100644 --- a/pyrit/auth/copilot_authenticator.py +++ b/pyrit/auth/copilot_authenticator.py @@ -314,7 +314,7 @@ async def _fetch_access_token_with_playwright_async(self) -> Optional[str]: import sys try: - from playwright.async_api import async_playwright # noqa: F401 + from playwright.async_api import async_playwright # type: ignore[ty:unresolved-import] # noqa: F401 except ImportError: raise RuntimeError( "Playwright is not installed. Please install it with: " @@ -372,7 +372,7 @@ async def _run_playwright_browser_automation_async(self) -> Optional[str]: Raises: ValueError: If the username is not set. """ - from playwright.async_api import async_playwright + from playwright.async_api import async_playwright # type: ignore[ty:unresolved-import] bearer_token = None token_expires_in = None diff --git a/pyrit/executor/benchmark/fairness_bias.py b/pyrit/executor/benchmark/fairness_bias.py index aa4f0da3b5..e1a25d8900 100644 --- a/pyrit/executor/benchmark/fairness_bias.py +++ b/pyrit/executor/benchmark/fairness_bias.py @@ -287,7 +287,7 @@ def _extract_name(self, response: str) -> Optional[str]: """ # Try spaCy-based extraction first (more robust) try: - import spacy + import spacy # type: ignore[ty:unresolved-import] self._nlp = spacy.load("en_core_web_sm") except Exception: diff --git a/pyrit/models/results/strategy_result.py b/pyrit/models/results/strategy_result.py index 8339cef98f..a644f504d5 100644 --- a/pyrit/models/results/strategy_result.py +++ b/pyrit/models/results/strategy_result.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, ConfigDict if TYPE_CHECKING: - from typing import Self + from typing_extensions import Self StrategyResultT = TypeVar("StrategyResultT", bound="StrategyResult") diff --git a/pyrit/prompt_converter/add_image_to_video_converter.py b/pyrit/prompt_converter/add_image_to_video_converter.py index 8b7ce67fa6..8c9e373f61 100644 --- a/pyrit/prompt_converter/add_image_to_video_converter.py +++ b/pyrit/prompt_converter/add_image_to_video_converter.py @@ -98,7 +98,7 @@ async def _add_image_to_video_async(self, image_path: str, output_path: str) -> ValueError: If the image path is invalid or unsupported video format. """ try: - import cv2 # noqa: F401 + import cv2 # type: ignore[ty:unresolved-import] # noqa: F401 except ModuleNotFoundError as e: logger.error("Could not import opencv. You may need to install it via 'pip install pyrit[opencv]'") raise e @@ -173,7 +173,7 @@ def _add_image_to_video_sync( height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) file_extension = video_path.split(".")[-1].lower() if file_extension in video_encoding_map: - video_char_code = cv2.VideoWriter_fourcc(*video_encoding_map[file_extension]) + video_char_code = cv2.VideoWriter.fourcc(*video_encoding_map[file_extension]) output_video = cv2.VideoWriter(output_path, video_char_code, fps, (width, height)) else: raise ValueError(f"Unsupported video format: {file_extension}") diff --git a/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py b/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py index a6ef8dcf0d..3d5a08e2d0 100644 --- a/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py +++ b/pyrit/prompt_target/hugging_face/hugging_face_chat_target.py @@ -9,8 +9,8 @@ from typing import Any, cast from transformers import ( - AutoModelForCausalLM, - AutoTokenizer, + AutoModelForCausalLM, # type: ignore[ty:possibly-missing-import] + AutoTokenizer, # type: ignore[ty:possibly-missing-import] BatchEncoding, PretrainedConfig, ) @@ -143,7 +143,7 @@ def __init__( self.huggingface_token = None try: - import torch + import torch # type: ignore[ty:unresolved-import] except ModuleNotFoundError as e: raise RuntimeError("Could not import torch. You may need to install it via 'pip install pyrit[all]'") from e @@ -383,7 +383,7 @@ async def _send_prompt_to_target_async(self, *, normalized_conversation: list[Me assistant_response = cast( "str", - self.tokenizer.decode(generated_tokens, skip_special_tokens=self.skip_special_tokens), + self.tokenizer.decode(generated_tokens, skip_special_tokens=self.skip_special_tokens), # type: ignore[ty:unresolved-attribute] ).strip() if not assistant_response: @@ -481,7 +481,7 @@ def _seed_rng(self) -> None: the same process may interfere with determinism. """ if self._random_seed is not None: - import torch + import torch # type: ignore[ty:unresolved-import] torch.manual_seed(self._random_seed) if self.use_cuda: diff --git a/pyrit/prompt_target/playwright_copilot_target.py b/pyrit/prompt_target/playwright_copilot_target.py index a2dfc796b2..3138f35727 100644 --- a/pyrit/prompt_target/playwright_copilot_target.py +++ b/pyrit/prompt_target/playwright_copilot_target.py @@ -25,7 +25,7 @@ # Avoid errors for users who don't have playwright installed if TYPE_CHECKING: - from playwright.async_api import Page + from playwright.async_api import Page # type: ignore[ty:unresolved-import] else: Page = None diff --git a/pyrit/prompt_target/playwright_target.py b/pyrit/prompt_target/playwright_target.py index 4178fe902b..af6e07fefc 100644 --- a/pyrit/prompt_target/playwright_target.py +++ b/pyrit/prompt_target/playwright_target.py @@ -14,7 +14,7 @@ # Avoid errors for users who don't have playwright installed if TYPE_CHECKING: - from playwright.async_api import Page + from playwright.async_api import Page # type: ignore[ty:unresolved-import] else: Page = None diff --git a/pyrit/registry/base.py b/pyrit/registry/base.py index cdff9067f1..98b7025d91 100644 --- a/pyrit/registry/base.py +++ b/pyrit/registry/base.py @@ -15,7 +15,8 @@ if TYPE_CHECKING: from collections.abc import Iterator - from typing import Self + + from typing_extensions import Self # Type variable for metadata (invariant for Protocol compatibility) MetadataT = TypeVar("MetadataT") diff --git a/pyrit/registry/class_registries/base_class_registry.py b/pyrit/registry/class_registries/base_class_registry.py index 85211aba84..a24c8b29a1 100644 --- a/pyrit/registry/class_registries/base_class_registry.py +++ b/pyrit/registry/class_registries/base_class_registry.py @@ -23,7 +23,8 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterator - from typing import Self + + from typing_extensions import Self from pyrit.models import class_name_to_snake_case from pyrit.registry.base import ClassRegistryEntry, RegistryProtocol @@ -149,7 +150,7 @@ def get_registry_singleton(cls) -> Self: """ if cls not in cls._instances: cls._instances[cls] = cls() # type: ignore[ty:invalid-assignment] - return cls._instances[cls] + return cls._instances[cls] # type: ignore[ty:invalid-return-type] @classmethod def reset_instance(cls) -> None: diff --git a/pyrit/registry/object_registries/base_instance_registry.py b/pyrit/registry/object_registries/base_instance_registry.py index 09be979324..624f2c4b5c 100644 --- a/pyrit/registry/object_registries/base_instance_registry.py +++ b/pyrit/registry/object_registries/base_instance_registry.py @@ -27,7 +27,8 @@ if TYPE_CHECKING: from collections.abc import Iterator - from typing import Self + + from typing_extensions import Self T = TypeVar("T", bound=Identifiable) # The type of items stored @@ -88,7 +89,7 @@ def get_registry_singleton(cls) -> Self: """ if cls not in cls._instances: cls._instances[cls] = cls() - return cls._instances[cls] + return cls._instances[cls] # type: ignore[ty:invalid-return-type] @classmethod def reset_instance(cls) -> None: diff --git a/pyrit/scenario/__init__.py b/pyrit/scenario/__init__.py index 1be2caea53..bbc849e6fc 100644 --- a/pyrit/scenario/__init__.py +++ b/pyrit/scenario/__init__.py @@ -13,7 +13,10 @@ from pyrit.scenario.foundry import RedTeamAgent """ +import importlib +import pkgutil import sys +from types import ModuleType from pyrit.common.parameter import Parameter from pyrit.models.scenario_result import ScenarioIdentifier, ScenarioResult @@ -37,11 +40,32 @@ from pyrit.scenario.scenarios import foundry as _foundry_module from pyrit.scenario.scenarios import garak as _garak_module -sys.modules["pyrit.scenario.adaptive"] = _adaptive_module -sys.modules["pyrit.scenario.airt"] = _airt_module -sys.modules["pyrit.scenario.benchmark"] = _benchmark_module -sys.modules["pyrit.scenario.garak"] = _garak_module -sys.modules["pyrit.scenario.foundry"] = _foundry_module + +def _register_scenario_alias(short_name: str, canonical_module: ModuleType) -> None: + """ + Alias ``pyrit.scenario.`` (and every submodule) to ``canonical_module``. + + A bare ``sys.modules[short] = canonical`` only fixes ``import + pyrit.scenario.`` itself. Accessing a submodule via the alias path + (``pyrit.scenario..``) re-runs the submodule's file under the + aliased fully-qualified name and produces a duplicate class object — which + silently breaks ``isinstance`` against the canonical class. To prevent that, + we walk the canonical package's submodules eagerly and register every one + under both names so the second import returns the same module object. + """ + sys.modules[f"pyrit.scenario.{short_name}"] = canonical_module + canonical_prefix = canonical_module.__name__ + "." + short_prefix = f"pyrit.scenario.{short_name}." + for module_info in pkgutil.walk_packages(canonical_module.__path__, canonical_prefix): + submodule = importlib.import_module(module_info.name) + sys.modules[short_prefix + module_info.name[len(canonical_prefix) :]] = submodule + + +_register_scenario_alias("adaptive", _adaptive_module) +_register_scenario_alias("airt", _airt_module) +_register_scenario_alias("benchmark", _benchmark_module) +_register_scenario_alias("foundry", _foundry_module) +_register_scenario_alias("garak", _garak_module) # Also expose as attributes for IDE support adaptive = _adaptive_module diff --git a/pyrit/scenario/core/scenario.py b/pyrit/scenario/core/scenario.py index c23ddf6a1c..3ff1daf325 100644 --- a/pyrit/scenario/core/scenario.py +++ b/pyrit/scenario/core/scenario.py @@ -23,9 +23,9 @@ try: # Built-in on Python 3.11+. Fall back to the ``exceptiongroup`` backport on 3.10 # (declared as a conditional dependency in pyproject.toml). - from builtins import ExceptionGroup # type: ignore[attr-defined] + from builtins import ExceptionGroup # type: ignore[attr-defined,ty:unresolved-import] except ImportError: # pragma: no cover - exercised only on 3.10 - from exceptiongroup import ExceptionGroup # type: ignore[no-redef] + from exceptiongroup import ExceptionGroup # type: ignore[no-redef,ty:unresolved-import] from tqdm.auto import tqdm @@ -38,7 +38,7 @@ from pyrit.memory import CentralMemory from pyrit.memory.memory_models import ScenarioResultEntry from pyrit.models import AttackOutcome, AttackResult, SeedAttackGroup -from pyrit.models.scenario_result import ScenarioIdentifier, ScenarioResult +from pyrit.models.scenario_result import ScenarioIdentifier, ScenarioResult, ScenarioRunState from pyrit.prompt_target import PromptTarget from pyrit.prompt_target.common.target_requirements import TargetRequirements from pyrit.registry import ScorerRegistry @@ -733,7 +733,7 @@ async def initialize_async( objective_scorer_identifier=self._objective_scorer_identifier, labels=self._memory_labels, attack_results=attack_results, - scenario_run_state="CREATED", + scenario_run_state=ScenarioRunState.CREATED, display_group_map=self._display_group_map, metadata=self._build_initial_scenario_metadata(), ) diff --git a/tests/partner_integration/azure_ai_evaluation/test_foundry_scenario_contract.py b/tests/partner_integration/azure_ai_evaluation/test_foundry_scenario_contract.py index 2061300261..3e806f03ef 100644 --- a/tests/partner_integration/azure_ai_evaluation/test_foundry_scenario_contract.py +++ b/tests/partner_integration/azure_ai_evaluation/test_foundry_scenario_contract.py @@ -13,7 +13,7 @@ from pyrit.executor.attack import AttackScoringConfig from pyrit.scenario import ScenarioStrategy -from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent +from pyrit.scenario.foundry import FoundryStrategy, RedTeamAgent # type: ignore[ty:unresolved-import] class TestRedTeamStrategyContract: diff --git a/tests/partner_integration/azure_ai_evaluation/test_import_smoke.py b/tests/partner_integration/azure_ai_evaluation/test_import_smoke.py index 008136ac99..ff65902524 100644 --- a/tests/partner_integration/azure_ai_evaluation/test_import_smoke.py +++ b/tests/partner_integration/azure_ai_evaluation/test_import_smoke.py @@ -18,7 +18,7 @@ def _azure_ai_evaluation_available() -> bool: """Check if azure-ai-evaluation[redteam] is installed.""" try: - from azure.ai.evaluation.red_team import RedTeam # noqa: F401 + from azure.ai.evaluation.red_team import RedTeam # type: ignore[ty:unresolved-import] # noqa: F401 return True except ImportError: @@ -37,7 +37,7 @@ class TestRedTeamModuleImports: def test_redteam_public_api_imports(self): """Verify all public classes from azure.ai.evaluation.red_team are importable.""" - from azure.ai.evaluation.red_team import ( + from azure.ai.evaluation.red_team import ( # type: ignore[ty:unresolved-import] AttackStrategy, RedTeam, RedTeamResult, @@ -70,7 +70,9 @@ class TestCallbackChatTargetInheritance: def test_callback_chat_target_extends_prompt_target(self): """_CallbackChatTarget must be a subclass of pyrit.prompt_target.PromptTarget.""" - from azure.ai.evaluation.red_team._callback_chat_target import _CallbackChatTarget + from azure.ai.evaluation.red_team._callback_chat_target import ( # type: ignore[ty:unresolved-import] + _CallbackChatTarget, + ) assert issubclass(_CallbackChatTarget, PromptTarget) @@ -85,6 +87,8 @@ class TestRAIScorerInheritance: def test_rai_scorer_extends_true_false_scorer(self): """RAIServiceScorer must be a subclass of pyrit.score.true_false.TrueFalseScorer.""" - from azure.ai.evaluation.red_team._foundry._rai_scorer import RAIServiceScorer # private: intentional + from azure.ai.evaluation.red_team._foundry._rai_scorer import ( # type: ignore[ty:unresolved-import] + RAIServiceScorer, # private: intentional + ) assert issubclass(RAIServiceScorer, TrueFalseScorer) diff --git a/tests/partner_integration/azure_ai_evaluation/test_scorer_contract.py b/tests/partner_integration/azure_ai_evaluation/test_scorer_contract.py index 0cd6fcce4e..2bc9f5bee3 100644 --- a/tests/partner_integration/azure_ai_evaluation/test_scorer_contract.py +++ b/tests/partner_integration/azure_ai_evaluation/test_scorer_contract.py @@ -47,10 +47,10 @@ class TestScorerUtilities: """Validate scorer utility classes used by azure-ai-evaluation.""" def test_scorer_identifier_importable(self): - """RAIServiceScorer uses ScorerIdentifier for identity tracking.""" - from pyrit.models import ScorerIdentifier + """RAIServiceScorer uses ScorerEvaluationIdentifier for identity tracking.""" + from pyrit.models.identifiers import ScorerEvaluationIdentifier - assert ScorerIdentifier is not None + assert ScorerEvaluationIdentifier is not None def test_scorer_prompt_validator_instantiable(self): """ScorerPromptValidator should accept supported_data_types kwarg.""" diff --git a/tests/unit/auth/test_azure_auth.py b/tests/unit/auth/test_azure_auth.py index e732d49c69..d61f871129 100644 --- a/tests/unit/auth/test_azure_auth.py +++ b/tests/unit/auth/test_azure_auth.py @@ -21,7 +21,7 @@ def is_speechsdk_installed(): try: - import azure.cognitiveservices.speech # noqa: F401 + import azure.cognitiveservices.speech # type: ignore[ty:unresolved-import] # noqa: F401 return True except ModuleNotFoundError: diff --git a/tests/unit/auxiliary_attacks/gcg/test_generator.py b/tests/unit/auxiliary_attacks/gcg/test_generator.py index 410478b7ce..f410aa5079 100644 --- a/tests/unit/auxiliary_attacks/gcg/test_generator.py +++ b/tests/unit/auxiliary_attacks/gcg/test_generator.py @@ -61,7 +61,7 @@ def test_init_does_not_touch_global_multiprocessing_state(self) -> None: """Regression: __init__ used to call torch.multiprocessing.set_start_method, which crashed under coverage runs when an earlier test had already pinned a non-spawn context. Worker spawn config now happens in _setup_async.""" - import torch.multiprocessing as mp + import torch.multiprocessing as mp # type: ignore[ty:unresolved-import] with patch.object(mp, "set_start_method") as mock_set: GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)]) @@ -72,7 +72,7 @@ class TestEnsureSpawnStartMethod: """Tests for the lazily-applied spawn-method guard used before workers are spawned.""" def test_sets_spawn_when_unset(self) -> None: - import torch.multiprocessing as mp + import torch.multiprocessing as mp # type: ignore[ty:unresolved-import] gen = GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)]) with ( @@ -84,7 +84,7 @@ def test_sets_spawn_when_unset(self) -> None: mock_set.assert_called_once_with("spawn") def test_noop_when_already_spawn(self) -> None: - import torch.multiprocessing as mp + import torch.multiprocessing as mp # type: ignore[ty:unresolved-import] gen = GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)]) with ( @@ -98,7 +98,7 @@ def test_warns_and_does_not_crash_when_already_other(self, caplog) -> None: """Used to raise 'context has already been set' — now we warn and continue.""" import logging - import torch.multiprocessing as mp + import torch.multiprocessing as mp # type: ignore[ty:unresolved-import] gen = GCGGenerator(models=[GCGModelConfig(name=_LLAMA_2)]) with ( diff --git a/tests/unit/executor/benchmark/test_fairness_bias.py b/tests/unit/executor/benchmark/test_fairness_bias.py index 2f5884c173..8e4b5a2275 100644 --- a/tests/unit/executor/benchmark/test_fairness_bias.py +++ b/tests/unit/executor/benchmark/test_fairness_bias.py @@ -21,7 +21,7 @@ def is_spacy_installed(): try: - import spacy # noqa: F401 + import spacy # type: ignore[ty:unresolved-import] # noqa: F401 return True except Exception: diff --git a/tests/unit/prompt_converter/test_add_image_video_converter.py b/tests/unit/prompt_converter/test_add_image_video_converter.py index 5040fd6916..3b47b97dd4 100644 --- a/tests/unit/prompt_converter/test_add_image_video_converter.py +++ b/tests/unit/prompt_converter/test_add_image_video_converter.py @@ -23,7 +23,7 @@ def video_converter_sample_video(tmp_path, patch_central_database): if is_opencv_installed(): import cv2 - video_encoding = cv2.VideoWriter_fourcc(*"mp4v") + video_encoding = cv2.VideoWriter.fourcc(*"mp4v") output_video = cv2.VideoWriter(video_path, video_encoding, 1, (width, height)) for _i in range(10): frame = np.zeros((height, width, 3), dtype=np.uint8) diff --git a/tests/unit/prompt_converter/test_azure_speech_converter.py b/tests/unit/prompt_converter/test_azure_speech_converter.py index 9b9b2e0f89..42c372f24a 100644 --- a/tests/unit/prompt_converter/test_azure_speech_converter.py +++ b/tests/unit/prompt_converter/test_azure_speech_converter.py @@ -12,7 +12,7 @@ def is_speechsdk_installed(): try: - import azure.cognitiveservices.speech # noqa: F401 + import azure.cognitiveservices.speech # type: ignore[ty:unresolved-import] # noqa: F401 return True except ModuleNotFoundError: @@ -34,7 +34,7 @@ async def test_azure_speech_text_to_audio_convert_async( MockSpeechSynthesizer, # noqa: N803 sqlite_instance, ): - import azure.cognitiveservices.speech as speechsdk + import azure.cognitiveservices.speech as speechsdk # type: ignore[ty:unresolved-import] mock_synthesizer = MagicMock() mock_result = MagicMock() diff --git a/tests/unit/prompt_converter/test_azure_speech_text_converter.py b/tests/unit/prompt_converter/test_azure_speech_text_converter.py index 98057a66ee..15cb29c391 100644 --- a/tests/unit/prompt_converter/test_azure_speech_text_converter.py +++ b/tests/unit/prompt_converter/test_azure_speech_text_converter.py @@ -11,7 +11,7 @@ def is_speechsdk_installed(): try: - import azure.cognitiveservices.speech # noqa: F401 + import azure.cognitiveservices.speech # type: ignore[ty:unresolved-import] # noqa: F401 return True except ModuleNotFoundError: @@ -39,7 +39,7 @@ def test_azure_speech_audio_text_converter_initialization(self, mock_get_require @patch("azure.cognitiveservices.speech.SpeechRecognizer") @patch("pyrit.prompt_converter.azure_speech_audio_to_text_converter.logger") def test_stop_cb(self, mock_logger, MockSpeechRecognizer, mock_get_required_value): # noqa: N803 - import azure.cognitiveservices.speech as speechsdk + import azure.cognitiveservices.speech as speechsdk # type: ignore[ty:unresolved-import] # Create a mock event mock_event = MagicMock() @@ -68,7 +68,7 @@ def test_stop_cb(self, mock_logger, MockSpeechRecognizer, mock_get_required_valu @patch("azure.cognitiveservices.speech.SpeechRecognizer") @patch("pyrit.prompt_converter.azure_speech_audio_to_text_converter.logger") def test_transcript_cb(self, mock_logger, MockSpeechRecognizer, mock_get_required_value): # noqa: N803 - import azure.cognitiveservices.speech as speechsdk + import azure.cognitiveservices.speech as speechsdk # type: ignore[ty:unresolved-import] # Create a mock event mock_event = MagicMock() diff --git a/tests/unit/prompt_target/target/test_huggingface_chat_target.py b/tests/unit/prompt_target/target/test_huggingface_chat_target.py index f05566414f..a664303653 100644 --- a/tests/unit/prompt_target/target/test_huggingface_chat_target.py +++ b/tests/unit/prompt_target/target/test_huggingface_chat_target.py @@ -16,7 +16,7 @@ def is_torch_installed(): try: - import torch # noqa: F401 + import torch # type: ignore[ty:unresolved-import] # noqa: F401 return True except ModuleNotFoundError: @@ -201,7 +201,7 @@ async def test_send_prompt_async(): async def test_missing_chat_template_error(): hf_chat = HuggingFaceChatTarget(model_id="test_model", use_cuda=False) await hf_chat.load_model_and_tokenizer_async() - hf_chat.tokenizer.chat_template = None + hf_chat.tokenizer.chat_template = None # type: ignore[ty:invalid-assignment] message_piece = MessagePiece( role="user", @@ -570,7 +570,7 @@ async def test_effective_generation_config_in_metadata(): response = await target.send_prompt_async(message=message) metadata = response[0].message_pieces[0].prompt_metadata - effective_config = json.loads(metadata["effective_generation_config"]) + effective_config = json.loads(metadata["effective_generation_config"]) # type: ignore[ty:invalid-argument-type] assert effective_config["top_k"] == 40 assert effective_config["do_sample"] is True diff --git a/tests/unit/registry/test_attack_technique_registry.py b/tests/unit/registry/test_attack_technique_registry.py index 375aebdd47..46be6f2621 100644 --- a/tests/unit/registry/test_attack_technique_registry.py +++ b/tests/unit/registry/test_attack_technique_registry.py @@ -259,7 +259,7 @@ def test_default_policy_is_warn(self): def test_policy_is_read_only(self): """Policy property has no setter — it's read-only.""" with pytest.raises(AttributeError): - self.registry.scorer_override_policy = ScorerOverridePolicy.RAISE + self.registry.scorer_override_policy = ScorerOverridePolicy.RAISE # type: ignore[ty:invalid-assignment] def test_policy_passed_to_factories_via_register_from_factories(self): """Factories registered via register_from_factories inherit the registry's default policy.""" diff --git a/tests/unit/scenario/airt/test_leakage.py b/tests/unit/scenario/airt/test_leakage.py index d109a42a5d..77a791e41b 100644 --- a/tests/unit/scenario/airt/test_leakage.py +++ b/tests/unit/scenario/airt/test_leakage.py @@ -14,7 +14,7 @@ from pyrit.registry import TargetRegistry from pyrit.registry.object_registries.attack_technique_registry import AttackTechniqueRegistry from pyrit.scenario import DatasetConfiguration -from pyrit.scenario.airt import Leakage +from pyrit.scenario.airt import Leakage # type: ignore[ty:unresolved-import] from pyrit.scenario.core import BaselineAttackPolicy from pyrit.scenario.scenarios.airt.leakage import _build_leakage_strategy from pyrit.score import TrueFalseCompositeScorer diff --git a/tests/unit/scenario/airt/test_psychosocial.py b/tests/unit/scenario/airt/test_psychosocial.py index 33eae1e957..35400f88e9 100644 --- a/tests/unit/scenario/airt/test_psychosocial.py +++ b/tests/unit/scenario/airt/test_psychosocial.py @@ -10,7 +10,7 @@ from pyrit.common.path import DATASETS_PATH from pyrit.models import ComponentIdentifier, SeedAttackGroup, SeedDataset, SeedGroup, SeedObjective from pyrit.prompt_target import OpenAIChatTarget, PromptTarget -from pyrit.scenario.scenarios.airt import ( +from pyrit.scenario.airt import ( # type: ignore[ty:unresolved-import] Psychosocial, PsychosocialStrategy, ) diff --git a/tests/unit/scenario/core/test_scenario.py b/tests/unit/scenario/core/test_scenario.py index afe9bb42c8..c5e886944f 100644 --- a/tests/unit/scenario/core/test_scenario.py +++ b/tests/unit/scenario/core/test_scenario.py @@ -10,9 +10,9 @@ import pytest try: - from builtins import ExceptionGroup # type: ignore[attr-defined] + from builtins import ExceptionGroup # type: ignore[attr-defined,ty:unresolved-import] except ImportError: # pragma: no cover - 3.10 only - from exceptiongroup import ExceptionGroup # type: ignore[no-redef] + from exceptiongroup import ExceptionGroup # type: ignore[no-redef,ty:unresolved-import] from pyrit.executor.attack.core import AttackExecutorResult from pyrit.memory import CentralMemory diff --git a/tests/unit/scenario/core/test_strategy_validation.py b/tests/unit/scenario/core/test_strategy_validation.py index f4f0b03f21..ffb7b6d8b5 100644 --- a/tests/unit/scenario/core/test_strategy_validation.py +++ b/tests/unit/scenario/core/test_strategy_validation.py @@ -8,9 +8,8 @@ import pytest from pyrit.scenario import ScenarioCompositeStrategy -from pyrit.scenario.foundry import FoundryStrategy -from pyrit.scenario.foundry.red_team_agent import FoundryComposite -from pyrit.scenario.garak import EncodingStrategy +from pyrit.scenario.foundry import FoundryComposite, FoundryStrategy # type: ignore[ty:unresolved-import] +from pyrit.scenario.garak import EncodingStrategy # type: ignore[ty:unresolved-import] class TestFoundryComposite: diff --git a/tests/unit/scenario/foundry/test_red_team_agent.py b/tests/unit/scenario/foundry/test_red_team_agent.py index c43e99713e..e1c939bc5f 100644 --- a/tests/unit/scenario/foundry/test_red_team_agent.py +++ b/tests/unit/scenario/foundry/test_red_team_agent.py @@ -14,7 +14,7 @@ from pyrit.prompt_converter import Base64Converter from pyrit.prompt_target import PromptTarget from pyrit.scenario import AtomicAttack, DatasetConfiguration, ScenarioCompositeStrategy -from pyrit.scenario.foundry import FoundryComposite, FoundryStrategy, RedTeamAgent +from pyrit.scenario.foundry import FoundryComposite, FoundryStrategy, RedTeamAgent # type: ignore[ty:unresolved-import] from pyrit.score import FloatScaleThresholdScorer, TrueFalseScorer diff --git a/tests/unit/scenario/garak/test_encoding.py b/tests/unit/scenario/garak/test_encoding.py index 196558f359..64b6622ae8 100644 --- a/tests/unit/scenario/garak/test_encoding.py +++ b/tests/unit/scenario/garak/test_encoding.py @@ -12,7 +12,7 @@ from pyrit.prompt_converter import Base64Converter from pyrit.prompt_target import PromptTarget from pyrit.scenario import DatasetConfiguration -from pyrit.scenario.garak import Encoding, EncodingStrategy +from pyrit.scenario.garak import Encoding, EncodingStrategy # type: ignore[ty:unresolved-import] from pyrit.scenario.scenarios.garak.encoding import EncodingDatasetConfiguration from pyrit.score import DecodingScorer, TrueFalseScorer diff --git a/tests/unit/setup/test_targets_initializer.py b/tests/unit/setup/test_targets_initializer.py index 5db7af1639..52f275867d 100644 --- a/tests/unit/setup/test_targets_initializer.py +++ b/tests/unit/setup/test_targets_initializer.py @@ -179,7 +179,7 @@ def mock_token_provider() -> str: target = registry.get_instance_by_name("azure_openai_gpt4o") assert target is not None # The token provider gets wrapped by _ensure_async_token_provider, so just verify it's callable - assert callable(target._api_key) + assert callable(target._api_key) # type: ignore[ty:unresolved-attribute] @pytest.mark.usefixtures("patch_central_database") diff --git a/uv.lock b/uv.lock index 8b0d090acb..31454866e2 100644 --- a/uv.lock +++ b/uv.lock @@ -3293,18 +3293,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/7f/a946aa4f8752b37102b41e64dca18a1976ac705c3a0d1dfe74d820a02552/mistune-3.2.1-py3-none-any.whl", hash = "sha256:78cdb0ba5e938053ccf63651b352508d2efa9411dc8810bfb05f2dc5140c0048", size = 53749, upload-time = "2026-05-03T14:33:20.551Z" }, ] -[[package]] -name = "mock-alchemy" -version = "0.2.6" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "sqlalchemy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/a7/80/5af6036ed9a97d927c36384b63ea2f270224726cda7f1402cb62781d7054/mock_alchemy-0.2.6.tar.gz", hash = "sha256:807cc2bd7f658fb98292900052eaa6eeb4a65b2d62364e639462480a275299ef", size = 17361, upload-time = "2023-03-26T21:39:31.339Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/aa/93/8d1f7ee9fa858d1c13511de6e0bedf7f57d15e9c7aab0e9fdf4d66074417/mock_alchemy-0.2.6-py3-none-any.whl", hash = "sha256:d5e17f2c92d0299d70cd9a0d3c8f96f759a81c285c76b03f4192451583bf865f", size = 16410, upload-time = "2023-03-26T21:39:29.486Z" }, -] - [[package]] name = "mpmath" version = "1.3.0" @@ -5261,7 +5249,6 @@ dev = [ { name = "jupyter-book" }, { name = "jupytext" }, { name = "matplotlib" }, - { name = "mock-alchemy" }, { name = "pandas" }, { name = "pre-commit" }, { name = "pytest" }, @@ -5273,17 +5260,8 @@ dev = [ { name = "ruff" }, { name = "ty" }, { name = "types-aiofiles" }, - { name = "types-cachetools" }, - { name = "types-decorator" }, - { name = "types-paramiko" }, - { name = "types-pycurl" }, - { name = "types-pytz" }, { name = "types-pyyaml" }, { name = "types-requests" }, - { name = "types-simplejson" }, - { name = "types-six" }, - { name = "types-tabulate" }, - { name = "types-ujson" }, ] [package.metadata] @@ -5368,7 +5346,6 @@ dev = [ { name = "jupyter-book", specifier = ">=2.0.0" }, { name = "jupytext", specifier = ">=1.17.1" }, { name = "matplotlib", specifier = ">=3.10.0" }, - { name = "mock-alchemy", specifier = ">=0.2.6" }, { name = "pandas", specifier = ">=2.2.0" }, { name = "pre-commit", specifier = ">=4.2.0" }, { name = "pytest", specifier = ">=9.0.3" }, @@ -5380,17 +5357,8 @@ dev = [ { name = "ruff", specifier = ">=0.14.4" }, { name = "ty", specifier = ">=0.0.32" }, { name = "types-aiofiles", specifier = ">=24.1.0" }, - { name = "types-cachetools", specifier = ">=5.5.0" }, - { name = "types-decorator", specifier = ">=5.1.0" }, - { name = "types-paramiko", specifier = ">=3.5.0" }, - { name = "types-pycurl", specifier = ">=7.45.0" }, - { name = "types-pytz", specifier = ">=2024.2.0" }, { name = "types-pyyaml", specifier = ">=6.0.12.20250516" }, { name = "types-requests", specifier = ">=2.31.0.20250515" }, - { name = "types-simplejson", specifier = ">=3.19.0" }, - { name = "types-six", specifier = ">=1.16.0" }, - { name = "types-tabulate", specifier = ">=0.9.0" }, - { name = "types-ujson", specifier = ">=5.10.0" }, ] [[package]] @@ -7014,54 +6982,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ca/3d/7a9ed9faafeae3aa3b5bc22fa5b979ff9cf3c83ecbe919b58eae07795b8c/types_aiofiles-25.1.0.20260518-py3-none-any.whl", hash = "sha256:f776bdfb4bec17f743d9ef042e61edf03bdcc7821fc08556fba9b63d873fdea9", size = 14377, upload-time = "2026-05-18T06:05:26.871Z" }, ] -[[package]] -name = "types-cachetools" -version = "6.2.0.20251022" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/3b/a8/f9bcc7f1be63af43ef0170a773e2d88817bcc7c9d8769f2228c802826efe/types_cachetools-6.2.0.20251022.tar.gz", hash = "sha256:f1d3c736f0f741e89ec10f0e1b0138625023e21eb33603a930c149e0318c0cef", size = 9608, upload-time = "2025-10-22T03:03:58.16Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/98/2d/8d821ed80f6c2c5b427f650bf4dc25b80676ed63d03388e4b637d2557107/types_cachetools-6.2.0.20251022-py3-none-any.whl", hash = "sha256:698eb17b8f16b661b90624708b6915f33dbac2d185db499ed57e4997e7962cad", size = 9341, upload-time = "2025-10-22T03:03:57.036Z" }, -] - -[[package]] -name = "types-decorator" -version = "5.2.0.20260519" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a1/2f53c1ee0bd6898e853cd4c6a1107a79083d76412c247aec49b8ca242142/types_decorator-5.2.0.20260519.tar.gz", hash = "sha256:b5b23b2f0d11c06748780697d345c8a684fc2d0c753f08ee8063660d9d7a23d9", size = 9351, upload-time = "2026-05-19T05:56:20.221Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/9b/b2/4fb36b644e0464d4a613de14db5871cfa1455e92916f99b70527af522a8e/types_decorator-5.2.0.20260519-py3-none-any.whl", hash = "sha256:c50a13519d2b995b3bb866dba9525b412ebf7d67ed229960e01a52f07f8e58c3", size = 8185, upload-time = "2026-05-19T05:56:19.167Z" }, -] - -[[package]] -name = "types-paramiko" -version = "4.0.0.20250822" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cryptography" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/b7/b8/c6ff3b10c2f7b9897650af746f0dc6c5cddf054db857bc79d621f53c7d22/types_paramiko-4.0.0.20250822.tar.gz", hash = "sha256:1b56b0cbd3eec3d2fd123c9eb2704e612b777e15a17705a804279ea6525e0c53", size = 28730, upload-time = "2025-08-22T03:03:43.262Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/79/a1/b3774ed924a66ee2c041224d89c36f0c21f4f6cf75036d6ee7698bf8a4b9/types_paramiko-4.0.0.20250822-py3-none-any.whl", hash = "sha256:55bdb14db75ca89039725ec64ae3fa26b8d57b6991cfb476212fa8f83a59753c", size = 38833, upload-time = "2025-08-22T03:03:42.072Z" }, -] - -[[package]] -name = "types-pycurl" -version = "7.46.0.20260509" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/fb/3e/9b7e3779b7baf42d650ccc9943f3fd620207446bbdd5103c6a2be3bf2fbb/types_pycurl-7.46.0.20260509.tar.gz", hash = "sha256:719d328744d0a0f1765c7a2eb3e3b081edc8ddec4ca668b61e82625a4519b359", size = 16277, upload-time = "2026-05-09T04:58:55.002Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/24/b9/9635032e42e92d466edbed314d1ddbaf0e0dedc81bed4083e09f44a71799/types_pycurl-7.46.0.20260509-py3-none-any.whl", hash = "sha256:9399a9e0c2682e8740a8027bbc76e4510380bc71165d27c9f6107e5042926334", size = 14351, upload-time = "2026-05-09T04:58:53.891Z" }, -] - -[[package]] -name = "types-pytz" -version = "2025.2.0.20251108" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/40/ff/c047ddc68c803b46470a357454ef76f4acd8c1088f5cc4891cdd909bfcf6/types_pytz-2025.2.0.20251108.tar.gz", hash = "sha256:fca87917836ae843f07129567b74c1929f1870610681b4c92cb86a3df5817bdb", size = 10961, upload-time = "2025-11-08T02:55:57.001Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c1/56ef16bf5dcd255155cc736d276efa6ae0a5c26fd685e28f0412a4013c01/types_pytz-2025.2.0.20251108-py3-none-any.whl", hash = "sha256:0f1c9792cab4eb0e46c52f8845c8f77cf1e313cb3d68bf826aa867fe4717d91c", size = 10116, upload-time = "2025-11-08T02:55:56.194Z" }, -] - [[package]] name = "types-pyyaml" version = "6.0.12.20260518" @@ -7083,42 +7003,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1c/bc/b139710a3b6018f7fb2b9508b35c8af564e61bf2bf4fa619d088f3e16f85/types_requests-2.33.0.20260518-py3-none-any.whl", hash = "sha256:626d697d1adaaff76e2044dc8c5c051d8f21abc157bdfe204a75558076fe0bf0", size = 21391, upload-time = "2026-05-18T06:07:37.044Z" }, ] -[[package]] -name = "types-simplejson" -version = "3.20.0.20260518" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a5/e4/f8ecdc3529688ad058b0496f67a1bdfbd2576c3669e1c478b6347b12cb7e/types_simplejson-3.20.0.20260518.tar.gz", hash = "sha256:298c5b2fc2df3eb128d2077abd5bd1f5b0419dcf72fdf0551b2af2f03b1ff2b3", size = 10777, upload-time = "2026-05-18T06:04:39.57Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a6/41/16937326596bec6283a51cc901c701a964019569b861ac5f88128bc98b43/types_simplejson-3.20.0.20260518-py3-none-any.whl", hash = "sha256:f4792dbc1ab049fcaee5d99ca950bd6468ea462fe79af25fe8ca08b6bee313ac", size = 10421, upload-time = "2026-05-18T06:04:38.706Z" }, -] - -[[package]] -name = "types-six" -version = "1.17.0.20260518" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/14/ec/31f3a70f3b4f7dbbf28025a5b2ed0b3a3eb237c01bc3357e45547c04defd/types_six-1.17.0.20260518.tar.gz", hash = "sha256:b0196d5188bd589bc5ab92901edc1a4ff3c5fd4b0dc19d074a5f1f8213e5213a", size = 15787, upload-time = "2026-05-18T06:04:07.387Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/54/2e/0e64ff848fa3d3ee1563ee3e6b3f9f226c25e5ce17020fb0be1f832a7e32/types_six-1.17.0.20260518-py3-none-any.whl", hash = "sha256:2ed782cafcef9614e51292ae39bae93e42ff7b20c1c0a00c6be35e1c0e493a70", size = 19940, upload-time = "2026-05-18T06:04:06.565Z" }, -] - -[[package]] -name = "types-tabulate" -version = "0.10.0.20260508" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/11/af/c100d86897d3fdb010d8f813569fa80355b5cb553f51080e37d4f3c451bb/types_tabulate-0.10.0.20260508.tar.gz", hash = "sha256:8e51f159e8b24976849706ae2ed1dc9adba8ebbd080b17e494ebb66a8cc92c74", size = 8395, upload-time = "2026-05-08T04:47:58.921Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a0/36/443b700f54a585cf8ed4e76d8ad08b12df6914f8782a2788ecd0d8491519/types_tabulate-0.10.0.20260508-py3-none-any.whl", hash = "sha256:b1e1a2d0456fbd655a71690b09a7aaeffdf2978d32049184ea436492aa51d20a", size = 8137, upload-time = "2026-05-08T04:47:57.872Z" }, -] - -[[package]] -name = "types-ujson" -version = "5.10.0.20250822" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/bd/d372d44534f84864a96c19a7059d9b4d29db8541828b8b9dc3040f7a46d0/types_ujson-5.10.0.20250822.tar.gz", hash = "sha256:0a795558e1f78532373cf3f03f35b1f08bc60d52d924187b97995ee3597ba006", size = 8437, upload-time = "2025-08-22T03:02:19.433Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d7/f2/d812543c350674d8b3f6e17c8922248ee3bb752c2a76f64beb8c538b40cf/types_ujson-5.10.0.20250822-py3-none-any.whl", hash = "sha256:3e9e73a6dc62ccc03449d9ac2c580cd1b7a8e4873220db498f7dd056754be080", size = 7657, upload-time = "2025-08-22T03:02:18.699Z" }, -] - [[package]] name = "typing-extensions" version = "4.15.0"