Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyrit/backend/services/converter_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from pyrit.models.data_type_serializer import data_serializer_factory
from pyrit.prompt_converter import PromptConverter
from pyrit.prompt_target import PromptChatTarget
from pyrit.registry.instance_registries import ConverterRegistry
from pyrit.registry.object_registries import ConverterRegistry

_DATA_TYPE_EXTENSION: dict[str, str] = {
"image_path": ".png",
Expand Down
2 changes: 1 addition & 1 deletion pyrit/backend/services/target_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
TargetListResponse,
)
from pyrit.prompt_target import PromptTarget
from pyrit.registry.instance_registries import TargetRegistry
from pyrit.registry.object_registries import TargetRegistry


def _build_target_class_registry() -> dict[str, type]:
Expand Down
10 changes: 8 additions & 2 deletions pyrit/registry/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""Registry module for PyRIT class and instance registries."""
"""Registry module for PyRIT class and object registries."""

from pyrit.registry.base import RegistryProtocol
from pyrit.registry.class_registries import (
Expand All @@ -17,16 +17,22 @@
discover_in_package,
discover_subclasses_in_loaded_modules,
)
from pyrit.registry.instance_registries import (
from pyrit.registry.object_registries import (
AttackTechniqueRegistry,
BaseInstanceRegistry,
ConverterRegistry,
RegistryEntry,
RetrievableInstanceRegistry,
ScorerRegistry,
TargetRegistry,
)

__all__ = [
"AttackTechniqueRegistry",
"BaseClassRegistry",
"BaseInstanceRegistry",
"ConverterRegistry",
"RetrievableInstanceRegistry",
"ClassEntry",
"discover_in_directory",
"discover_in_package",
Expand Down
15 changes: 10 additions & 5 deletions pyrit/registry/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
Shared base types for PyRIT registries.

This module contains types shared between class registries (which store Type[T])
and instance registries (which store T instances).
and object registries (which store T instances).
"""

from collections.abc import Iterator
from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Optional, Protocol, TypeVar, runtime_checkable
from typing import TYPE_CHECKING, Any, Optional, Protocol, TypeVar, runtime_checkable

if TYPE_CHECKING:
from collections.abc import Iterator
from typing import Self

# Type variable for metadata (invariant for Protocol compatibility)
MetadataT = TypeVar("MetadataT")
Expand Down Expand Up @@ -43,7 +48,7 @@ class RegistryProtocol(Protocol[MetadataT]):
"""
Protocol defining the common interface for all registries.

Both class registries (BaseClassRegistry) and instance registries
Both class registries (BaseClassRegistry) and object registries
(BaseInstanceRegistry) implement this interface, enabling code that
works with either registry type.

Expand All @@ -52,7 +57,7 @@ class RegistryProtocol(Protocol[MetadataT]):
"""

@classmethod
def get_registry_singleton(cls) -> "RegistryProtocol[MetadataT]":
def get_registry_singleton(cls) -> Self:
"""Get the singleton instance of this registry."""
...

Expand Down
2 changes: 1 addition & 1 deletion pyrit/registry/class_registries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
This package contains registries that store classes (Type[T]) which can be
instantiated on demand. Examples include ScenarioRegistry and InitializerRegistry.

For registries that store pre-configured instances, see instance_registries/.
For registries that store pre-configured instances, see object_registries/.
"""

from pyrit.registry.class_registries.base_class_registry import (
Expand Down
15 changes: 10 additions & 5 deletions pyrit/registry/class_registries/base_class_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
This module provides the abstract base class for registries that store classes (Type[T]).
These registries allow on-demand instantiation of registered classes.

For registries that store pre-configured instances, see instance_registries/.
For registries that store pre-configured instances, see object_registries/.

Terminology:
- **Metadata**: A TypedDict describing a registered class (e.g., ScenarioMetadata)
Expand All @@ -16,9 +16,14 @@
- **ClassEntry**: Internal wrapper holding a class plus optional factory/defaults
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from collections.abc import Callable, Iterator
from typing import Generic, Optional, TypeVar
from typing import TYPE_CHECKING, Generic, Optional, TypeVar

if TYPE_CHECKING:
from collections.abc import Callable, Iterator
from typing import Self

from pyrit.identifiers.class_name_utils import class_name_to_snake_case
from pyrit.registry.base import RegistryProtocol
Expand Down Expand Up @@ -107,7 +112,7 @@ class BaseClassRegistry(ABC, RegistryProtocol[MetadataT], Generic[T, MetadataT])
"""

# Class-level singleton instances, keyed by registry class
_instances: dict[type, "BaseClassRegistry[object, object]"] = {}
_instances: dict[type, BaseClassRegistry[object, object]] = {}

def __init__(self, *, lazy_discovery: bool = True) -> None:
"""
Expand All @@ -128,7 +133,7 @@ def __init__(self, *, lazy_discovery: bool = True) -> None:
self._discovered = True

@classmethod
def get_registry_singleton(cls) -> "BaseClassRegistry[T, MetadataT]":
def get_registry_singleton(cls) -> Self:
"""
Get the singleton instance of this registry.

Expand Down
10 changes: 0 additions & 10 deletions pyrit/registry/class_registries/initializer_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,16 +66,6 @@ class InitializerRegistry(BaseClassRegistry["PyRITInitializer", InitializerMetad
The directory structure is used for organization but not exposed to users.
"""

@classmethod
def get_registry_singleton(cls) -> InitializerRegistry:
"""
Get the singleton instance of the InitializerRegistry.

Returns:
The singleton InitializerRegistry instance.
"""
return super().get_registry_singleton() # type: ignore[return-value]

def __init__(self, *, discovery_path: Optional[Path] = None, lazy_discovery: bool = False) -> None:
"""
Initialize the initializer registry.
Expand Down
10 changes: 0 additions & 10 deletions pyrit/registry/class_registries/scenario_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,6 @@ class ScenarioRegistry(BaseClassRegistry["Scenario", ScenarioMetadata]):
Scenarios are identified by their dotted name (e.g., "garak.encoding", "foundry.red_team_agent").
"""

@classmethod
def get_registry_singleton(cls) -> ScenarioRegistry:
"""
Get the singleton instance of the ScenarioRegistry.

Returns:
The singleton ScenarioRegistry instance.
"""
return super().get_registry_singleton() # type: ignore[return-value]

def __init__(self, *, lazy_discovery: bool = True) -> None:
"""
Initialize the scenario registry.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the MIT license.

"""
Instance registries package.
Object registries package.

This package contains registries that store pre-configured instances (not classes).
Examples include ScorerRegistry which stores Scorer instances that have been
Expand All @@ -11,25 +11,33 @@
For registries that store classes (Type[T]), see class_registries/.
"""

from pyrit.registry.instance_registries.base_instance_registry import (
from pyrit.registry.object_registries.attack_technique_registry import (
AttackTechniqueRegistry,
)
from pyrit.registry.object_registries.base_instance_registry import (
BaseInstanceRegistry,
RegistryEntry,
)
from pyrit.registry.instance_registries.converter_registry import (
from pyrit.registry.object_registries.converter_registry import (
ConverterRegistry,
)
from pyrit.registry.instance_registries.scorer_registry import (
from pyrit.registry.object_registries.retrievable_instance_registry import (
RetrievableInstanceRegistry,
)
from pyrit.registry.object_registries.scorer_registry import (
ScorerRegistry,
)
from pyrit.registry.instance_registries.target_registry import (
from pyrit.registry.object_registries.target_registry import (
TargetRegistry,
)

__all__ = [
# Base class
# Base classes
"BaseInstanceRegistry",
"RetrievableInstanceRegistry",
"RegistryEntry",
# Concrete registries
"AttackTechniqueRegistry",
"ConverterRegistry",
"ScorerRegistry",
"TargetRegistry",
Expand Down
95 changes: 95 additions & 0 deletions pyrit/registry/object_registries/attack_technique_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

"""
AttackTechniqueRegistry — Singleton registry of reusable attack technique factories.

Scenarios and initializers register technique factories (capturing technique-specific
config). Scenarios retrieve them via ``create_technique()``, which calls the factory
with the scenario's objective target and scorer.
"""
Comment thread
rlundeen2 marked this conversation as resolved.

from __future__ import annotations

import logging
from typing import TYPE_CHECKING

from pyrit.registry.object_registries.base_instance_registry import (
BaseInstanceRegistry,
)

if TYPE_CHECKING:
from pyrit.executor.attack.core.attack_config import (
AttackAdversarialConfig,
AttackConverterConfig,
AttackScoringConfig,
)
from pyrit.prompt_target import PromptTarget
from pyrit.scenario.core.attack_technique import AttackTechnique
from pyrit.scenario.core.attack_technique_factory import AttackTechniqueFactory

logger = logging.getLogger(__name__)


class AttackTechniqueRegistry(BaseInstanceRegistry["AttackTechniqueFactory"]):
"""
Singleton registry of reusable attack technique factories.

Scenarios and initializers register technique factories (capturing
technique-specific config). Scenarios retrieve them via ``create_technique()``,
Comment thread
rlundeen2 marked this conversation as resolved.
which calls the factory with the scenario's objective target and scorer.
"""

def register_technique(
self,
*,
name: str,
factory: AttackTechniqueFactory,
Comment thread
rlundeen2 marked this conversation as resolved.
tags: dict[str, str] | list[str] | None = None,
) -> None:
"""
Register an attack technique factory.

Args:
name: The registry name for this technique.
factory: The factory that produces attack techniques.
tags: Optional tags for categorisation. Accepts a ``dict[str, str]``
or a ``list[str]`` (each string becomes a key with value ``""``).
"""
self.register(factory, name=name, tags=tags)
logger.debug(f"Registered attack technique factory: {name} ({factory.attack_class.__name__})")

def create_technique(
self,
name: str,
*,
objective_target: PromptTarget,
attack_scoring_config: AttackScoringConfig,
attack_adversarial_config: AttackAdversarialConfig | None = None,
attack_converter_config: AttackConverterConfig | None = None,
Comment thread
rlundeen2 marked this conversation as resolved.
) -> AttackTechnique:
"""
Retrieve a factory by name and produce a fresh attack technique.

Args:
name: The registry name of the technique.
objective_target: The target to attack.
attack_scoring_config: Scoring configuration for the attack.
attack_adversarial_config: Optional adversarial configuration override.
attack_converter_config: Optional converter configuration override.

Returns:
A fresh AttackTechnique with a newly-constructed attack strategy.

Raises:
KeyError: If no technique is registered with the given name.
"""
entry = self._registry_items.get(name)
if entry is None:
raise KeyError(f"No technique registered with name '{name}'")
return entry.instance.create(
objective_target=objective_target,
attack_scoring_config=attack_scoring_config,
attack_adversarial_config=attack_adversarial_config,
attack_converter_config=attack_converter_config,
)
Loading
Loading