updates
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
from .base import Event, PayloadBase
|
||||
from .context import EventContext
|
||||
|
||||
from .types import EventTypeBase, EventType, ParamSource, SourceType
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Event",
|
||||
"PayloadBase",
|
||||
"EventContext",
|
||||
"EventTypeBase",
|
||||
"EventType",
|
||||
"ParamSource",
|
||||
"SourceType",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
from typing import Generic, Optional, TypeVar
|
||||
from typing_extensions import Annotated
|
||||
import uuid
|
||||
from pydantic import UUID4, BaseModel, BeforeValidator, Field
|
||||
|
||||
from .types import EventTypeBase, SourceType
|
||||
|
||||
|
||||
class PayloadBase(BaseModel):
|
||||
"""
|
||||
Base class for all event payloads
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Generics
|
||||
PayloadT = TypeVar("PayloadT", bound=PayloadBase)
|
||||
EventTypeT = TypeVar("EventTypeT", bound=EventTypeBase)
|
||||
|
||||
|
||||
def convert_to_source_type(v):
|
||||
if isinstance(v, str):
|
||||
try:
|
||||
return SourceType(v)
|
||||
except ValueError:
|
||||
pass
|
||||
return v
|
||||
|
||||
|
||||
class Event(BaseModel, Generic[EventTypeT, PayloadT]):
|
||||
id: UUID4 = Field(default_factory=lambda: uuid.uuid4())
|
||||
timestamp: int
|
||||
type: EventTypeT
|
||||
source: Annotated[SourceType, BeforeValidator(convert_to_source_type)]
|
||||
correlation_id: Optional[str] = Field(
|
||||
description="Unique identifier for tracing related events",
|
||||
default=None,
|
||||
)
|
||||
payload: PayloadT
|
||||
@@ -0,0 +1,9 @@
|
||||
SAFETY_NAMESPACE = "safetycli"
|
||||
PRODUCT_CLI = "cli"
|
||||
GITHUB = "github"
|
||||
PYPI = "pypi"
|
||||
DOCKER = "docker"
|
||||
ACTION = "action"
|
||||
APP = "app"
|
||||
|
||||
CLI_SOURCE = f"urn:{SAFETY_NAMESPACE}:{PRODUCT_CLI}"
|
||||
@@ -0,0 +1,114 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
|
||||
from .types import SourceType
|
||||
|
||||
from typing_extensions import Annotated
|
||||
from pydantic.types import StringConstraints
|
||||
|
||||
class ClientInfo(BaseModel):
|
||||
"""
|
||||
Information about the client application.
|
||||
"""
|
||||
|
||||
identifier: SourceType = Field(description="Client source identifier name")
|
||||
version: str = Field(description="Client application version")
|
||||
path: str = Field(description="Path to the client executable")
|
||||
|
||||
|
||||
class ProjectInfo(BaseModel):
|
||||
"""
|
||||
Information about the project context.
|
||||
"""
|
||||
|
||||
id: str = Field(default="unknown", description="Project identifier")
|
||||
url: Optional[str] = Field(default=None, description="Project URL")
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
"""
|
||||
Information about the user.
|
||||
"""
|
||||
|
||||
name: str = Field(description="Username")
|
||||
home_dir: str = Field(description="User's home directory")
|
||||
|
||||
|
||||
class OsInfo(BaseModel):
|
||||
"""
|
||||
Information about the operating system.
|
||||
"""
|
||||
architecture: Annotated[str, StringConstraints(to_lower=True)] = Field(description="Machine architecture")
|
||||
platform: Annotated[str, StringConstraints(to_lower=True)] = Field(description="Operating system platform")
|
||||
name: Annotated[Optional[str], StringConstraints(to_lower=True)] = Field(description="Operating system name")
|
||||
version: Annotated[Optional[str], StringConstraints(to_lower=True)] = Field(description="Operating system version")
|
||||
kernel_version: Annotated[Optional[str], StringConstraints(to_lower=True)] = Field(
|
||||
default=None, description="Kernel version if available"
|
||||
)
|
||||
|
||||
|
||||
class HostInfo(BaseModel):
|
||||
"""
|
||||
Information about the host machine.
|
||||
"""
|
||||
|
||||
name: str = Field(description="Hostname")
|
||||
ipv4: Optional[str] = Field(default=None, description="IPv4 address")
|
||||
ipv6: Optional[str] = Field(default=None, description="IPv6 address")
|
||||
timezone: Optional[str] = Field(default=None, description="Timezone")
|
||||
|
||||
|
||||
class PythonInfo(BaseModel):
|
||||
"""
|
||||
Detailed information about the Python environment.
|
||||
"""
|
||||
|
||||
version: str = Field(description="Python version (major.minor)")
|
||||
path: str = Field(description="Path to the Python executable")
|
||||
sys_path: List[str] = Field(description="Python sys.path")
|
||||
implementation: Optional[str] = Field(
|
||||
default=None, description="Python implementation (e.g., 'CPython')"
|
||||
)
|
||||
implementation_version: Optional[str] = Field(
|
||||
default=None, description="Python implementation version"
|
||||
)
|
||||
|
||||
sys_prefix: str = Field(description="sys.prefix location")
|
||||
site_packages: List[str] = Field(description="List of site-packages directories")
|
||||
user_site_enabled: bool = Field(
|
||||
description="Whether user site-packages are enabled for imports"
|
||||
)
|
||||
user_site_packages: Optional[str] = Field(
|
||||
default=None, description="User site-packages directory path if available"
|
||||
)
|
||||
|
||||
encoding: str = Field(description="Default string encoding")
|
||||
filesystem_encoding: str = Field(description="Filesystem encoding")
|
||||
|
||||
|
||||
class RuntimeInfo(BaseModel):
|
||||
"""
|
||||
Information about the runtime environment.
|
||||
"""
|
||||
|
||||
workdir: str = Field(description="Working directory")
|
||||
user: UserInfo = Field(description="User information")
|
||||
os: OsInfo = Field(description="Operating system information")
|
||||
host: HostInfo = Field(description="Host information")
|
||||
python: Optional[PythonInfo] = Field(default=None, description="Python information")
|
||||
|
||||
|
||||
class EventContext(BaseModel):
|
||||
"""
|
||||
Complete context information for an event.
|
||||
Contains details about the client, project, and runtime environment.
|
||||
"""
|
||||
|
||||
client: ClientInfo = Field(description="Client application information")
|
||||
runtime: RuntimeInfo = Field(description="Runtime environment information")
|
||||
project: Optional[ProjectInfo] = Field(
|
||||
default=None, description="Project information"
|
||||
)
|
||||
tags: Optional[List[str]] = Field(
|
||||
default=None, description="Event tags for categorization"
|
||||
)
|
||||
@@ -0,0 +1,68 @@
|
||||
from .main import (
|
||||
CommandParam,
|
||||
CommandExecutedPayload,
|
||||
CommandErrorPayload,
|
||||
PackagePayloadBase,
|
||||
SingleVersionPackagePayload,
|
||||
ToolCommandExecutedPayload,
|
||||
PackageInstalledPayload,
|
||||
PackageUninstalledPayload,
|
||||
PackageUpdatedPayload,
|
||||
HealthCheckResult,
|
||||
IndexConfig,
|
||||
AliasConfig,
|
||||
ToolStatus,
|
||||
FirewallConfiguredPayload,
|
||||
FirewallDisabledPayload,
|
||||
FirewallHeartbeatPayload,
|
||||
ProcessStatus,
|
||||
)
|
||||
|
||||
from .onboarding import (
|
||||
InitStartedPayload,
|
||||
AuthStartedPayload,
|
||||
AuthCompletedPayload,
|
||||
FirewallSetupResponseCreatedPayload,
|
||||
FirewallSetupCompletedPayload,
|
||||
CodebaseSetupResponseCreatedPayload,
|
||||
CodebaseSetupCompletedPayload,
|
||||
DependencyFile,
|
||||
CodebaseDetectionStatusPayload,
|
||||
InitScanCompletedPayload,
|
||||
InitExitStep,
|
||||
InitExitedPayload,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"CommandParam",
|
||||
"CommandExecutedPayload",
|
||||
"CommandErrorPayload",
|
||||
"PackagePayloadBase",
|
||||
"SingleVersionPackagePayload",
|
||||
"PackageInstalledPayload",
|
||||
"PackageUninstalledPayload",
|
||||
"PackageUpdatedPayload",
|
||||
"HealthCheckResult",
|
||||
"IndexConfig",
|
||||
"AliasConfig",
|
||||
"ToolStatus",
|
||||
"FirewallConfiguredPayload",
|
||||
"FirewallDisabledPayload",
|
||||
"FirewallHeartbeatPayload",
|
||||
"ProcessStatus",
|
||||
"ToolCommandExecutedPayload",
|
||||
|
||||
# Onboarding
|
||||
"InitStartedPayload",
|
||||
"AuthStartedPayload",
|
||||
"AuthCompletedPayload",
|
||||
"FirewallSetupResponseCreatedPayload",
|
||||
"FirewallSetupCompletedPayload",
|
||||
"CodebaseSetupResponseCreatedPayload",
|
||||
"CodebaseSetupCompletedPayload",
|
||||
"DependencyFile",
|
||||
"CodebaseDetectionStatusPayload",
|
||||
"InitScanCompletedPayload",
|
||||
"InitExitStep",
|
||||
"InitExitedPayload",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,223 @@
|
||||
from typing import Any, List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..base import PayloadBase
|
||||
from ..types import LimitedStr, ParamSource, StackTrace, StdErr, StdOut, ToolType
|
||||
|
||||
|
||||
class CommandParam(BaseModel):
|
||||
position: int = Field(description="Position in the original command")
|
||||
name: Optional[LimitedStr] = Field(
|
||||
default=None, description="Name of the option, None for positional arguments"
|
||||
)
|
||||
value: Any = Field(description="Value of the argument or option")
|
||||
source: ParamSource = Field(
|
||||
ParamSource.UNKNOWN,
|
||||
description="Source of the parameter value (commandline, environment, config, default, prompt)",
|
||||
)
|
||||
|
||||
@property
|
||||
def is_option(self) -> bool:
|
||||
"""
|
||||
Return True if this is a named option, False if positional argument
|
||||
"""
|
||||
return self.name is not None
|
||||
|
||||
|
||||
class ProcessStatus(BaseModel):
|
||||
stdout: Optional[StdOut] = Field(
|
||||
default=None, description="Standard output of the process"
|
||||
)
|
||||
stderr: Optional[StdErr] = Field(
|
||||
default=None, description="Standard error of the process"
|
||||
)
|
||||
return_code: int = Field(description="Return code of the process")
|
||||
|
||||
|
||||
class CommandExecutedPayload(PayloadBase):
|
||||
command_name: str = Field(
|
||||
description="Primary command name (e.g., 'status', 'scan')"
|
||||
)
|
||||
command_path: List[LimitedStr] = Field(
|
||||
description="Command path as a list (e.g., ['safety', 'auth', 'login'])"
|
||||
)
|
||||
raw_command: List[LimitedStr] = Field(
|
||||
description="Complete command as a list (equivalent to sys.argv)"
|
||||
)
|
||||
parameters: List[CommandParam] = Field(
|
||||
description="Parameters defined by the us", default_factory=list
|
||||
)
|
||||
duration_ms: int = Field(
|
||||
gt=0,
|
||||
description="Execution time in milliseconds for the full command "
|
||||
"including any tool call",
|
||||
)
|
||||
status: ProcessStatus = Field(
|
||||
description="Status data (stdout/stderr/return_code) when applicable"
|
||||
)
|
||||
|
||||
|
||||
class ToolCommandExecutedPayload(PayloadBase):
|
||||
"""
|
||||
Information about a wrapped command execution.
|
||||
"""
|
||||
tool: ToolType = Field(
|
||||
description="Tool Type (e.g., 'pip', 'uv', 'poetry', 'npm')"
|
||||
)
|
||||
tool_path: Optional[str] = Field(default=None, description="Absolute path to the tool's executable")
|
||||
raw_command: List[LimitedStr] = Field(
|
||||
description="Complete command as a list (equivalent to sys.argv)"
|
||||
)
|
||||
duration_ms: int = Field(
|
||||
gt=0,
|
||||
description="Execution time in milliseconds",
|
||||
)
|
||||
status: ProcessStatus = Field(
|
||||
description="Status data (stdout/stderr/return_code) when applicable"
|
||||
)
|
||||
|
||||
|
||||
class CommandErrorPayload(PayloadBase):
|
||||
command_name: Optional[LimitedStr] = Field(
|
||||
description="Name of the command that failed"
|
||||
)
|
||||
command_path: Optional[List[LimitedStr]] = Field(
|
||||
description="Command path as a list (e.g., ['safety', 'auth', 'login'])"
|
||||
)
|
||||
raw_command: List[LimitedStr] = Field(
|
||||
description="Complete command as a list (equivalent to sys.argv)"
|
||||
)
|
||||
error_message: str = Field(description="Error message")
|
||||
stacktrace: Optional[StackTrace] = Field(
|
||||
default=None, description="Stack trace if available"
|
||||
)
|
||||
|
||||
|
||||
class PackagePayloadBase(PayloadBase):
|
||||
package_name: str = Field(description="Name of the package")
|
||||
tool: ToolType = Field(description="ToolType used (e.g., pip, conda)")
|
||||
tool_path: Optional[str] = Field(default=None, description="Absolute path to the tool's executable")
|
||||
location: Optional[str] = Field(default=None, description="Location of the package")
|
||||
|
||||
|
||||
class SingleVersionPackagePayload(PackagePayloadBase):
|
||||
version: str = Field(description="Version of the package")
|
||||
|
||||
|
||||
class PackageInstalledPayload(SingleVersionPackagePayload):
|
||||
pass
|
||||
|
||||
|
||||
class PackageUninstalledPayload(SingleVersionPackagePayload):
|
||||
pass
|
||||
|
||||
|
||||
class PackageUpdatedPayload(PackagePayloadBase):
|
||||
previous_version: str = Field(description="Previous package version")
|
||||
current_version: str = Field(description="Current package version")
|
||||
|
||||
|
||||
class HealthCheckResult(BaseModel):
|
||||
"""
|
||||
Generic health check result structure.
|
||||
"""
|
||||
|
||||
is_alive: bool = Field(description="Whether the entity is alive and responding")
|
||||
response_time_ms: Optional[int] = Field(
|
||||
None, description="Response time in milliseconds"
|
||||
)
|
||||
error_message: Optional[LimitedStr] = Field(
|
||||
None, description="Error message if any"
|
||||
)
|
||||
timestamp: str = Field(description="When the health check was performed")
|
||||
|
||||
|
||||
class IndexConfig(BaseModel):
|
||||
"""
|
||||
Configuration details for the package index.
|
||||
"""
|
||||
|
||||
is_configured: bool = Field(
|
||||
description="Whether the index configuration is in place"
|
||||
)
|
||||
index_url: Optional[LimitedStr] = Field(
|
||||
default=None, description="URL of the configured package index"
|
||||
)
|
||||
health_check: Optional[HealthCheckResult] = Field(
|
||||
default=None, description="Health check for the index"
|
||||
)
|
||||
|
||||
|
||||
class AliasConfig(BaseModel):
|
||||
"""
|
||||
Configuration details for the command alias.
|
||||
"""
|
||||
|
||||
is_configured: bool = Field(description="Whether the alias is configured")
|
||||
alias_content: Optional[LimitedStr] = Field(
|
||||
default=None, description="Content of the alias"
|
||||
)
|
||||
health_check: Optional[HealthCheckResult] = Field(
|
||||
default=None, description="Health check for the alias"
|
||||
)
|
||||
|
||||
|
||||
class ToolStatus(BaseModel):
|
||||
"""
|
||||
Status of a single package manager tool. A single package manager tool is
|
||||
being identified by its executable path.
|
||||
"""
|
||||
|
||||
type: ToolType = Field(description="Tool type")
|
||||
command_path: str = Field(description="Absolute path to the tool's executable")
|
||||
version: str = Field(description="Version of the tool")
|
||||
reachable: bool = Field(
|
||||
description="Whether the tool's package manager is reachable bypassing any firewall setup"
|
||||
)
|
||||
|
||||
# Configuration information
|
||||
alias_config: Optional[AliasConfig] = Field(
|
||||
default=None, description="Details about the alias configuration"
|
||||
)
|
||||
index_config: Optional[IndexConfig] = Field(
|
||||
default=None, description="Details about the index configuration"
|
||||
)
|
||||
|
||||
@property
|
||||
def alias_configured(self) -> bool:
|
||||
"""
|
||||
Whether the alias is configured.
|
||||
"""
|
||||
return self.alias_config is not None and self.alias_config.is_configured
|
||||
|
||||
@property
|
||||
def index_configured(self) -> bool:
|
||||
"""
|
||||
Whether the index is configured.
|
||||
"""
|
||||
return self.index_config is not None and self.index_config.is_configured
|
||||
|
||||
@property
|
||||
def is_configured(self) -> bool:
|
||||
"""
|
||||
Returns whether the tool is fully configured (both alias and index).
|
||||
"""
|
||||
return self.alias_configured and self.index_configured
|
||||
|
||||
|
||||
class FirewallConfiguredPayload(PayloadBase):
|
||||
tools: List[ToolStatus] = Field(
|
||||
description="Status of all detected package manager tools"
|
||||
)
|
||||
|
||||
|
||||
class FirewallDisabledPayload(PayloadBase):
|
||||
reason: Optional[LimitedStr] = Field(
|
||||
description="Reason for disabling the firewall"
|
||||
)
|
||||
|
||||
|
||||
class FirewallHeartbeatPayload(PayloadBase):
|
||||
tools: List[ToolStatus] = Field(
|
||||
description="Status of all detected package manager tools"
|
||||
)
|
||||
@@ -0,0 +1,136 @@
|
||||
from typing import List, Optional, Dict, Any
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ..base import PayloadBase
|
||||
from ..types import LimitedStr, ToolType
|
||||
from .main import ToolStatus
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class InitStartedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Init Started event.
|
||||
This is emitted when the init command is started.
|
||||
Note: This event is typically delayed until the user completes authentication.
|
||||
"""
|
||||
# This is an empty payload as the timestamp is already in the event
|
||||
pass
|
||||
|
||||
|
||||
class AuthStartedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Auth Started event.
|
||||
This is emitted when the authentication flow is initiated and a URL is shown to the user.
|
||||
"""
|
||||
auth_url: Optional[LimitedStr] = Field(
|
||||
default=None, description="URL provided to the user for authentication"
|
||||
)
|
||||
|
||||
|
||||
class AuthCompletedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Auth Completed event.
|
||||
This is emitted when the authentication flow is completed.
|
||||
"""
|
||||
success: bool = Field(description="Whether authentication was successful")
|
||||
error_message: Optional[LimitedStr] = Field(
|
||||
default=None, description="Error message if authentication failed"
|
||||
)
|
||||
|
||||
|
||||
class FirewallSetupResponseCreatedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Firewall Setup Response Created event.
|
||||
This captures the user's choice to install the firewall (Y/N).
|
||||
"""
|
||||
user_consent_requested: bool = Field(
|
||||
description="Whether the user was asked for consent to install the firewall"
|
||||
)
|
||||
user_consent: Optional[bool] = Field(
|
||||
default=None, description="User's consent to install the firewall (True for yes, False for no, None if unknown)"
|
||||
)
|
||||
|
||||
|
||||
class FirewallSetupCompletedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Firewall Setup Completed event.
|
||||
This is emitted when the firewall is configured. This payload has the current status of all tools.
|
||||
"""
|
||||
tools: List[ToolStatus] = Field(
|
||||
description="Status of all configured package manager tools"
|
||||
)
|
||||
|
||||
|
||||
class DependencyFile(BaseModel):
|
||||
"""
|
||||
Information about a detected dependency file.
|
||||
"""
|
||||
file_path: str = Field(description="Path to the detected dependency file")
|
||||
|
||||
|
||||
class CodebaseDetectionStatusPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Codebase Detection Status event.
|
||||
This is emitted when the codebase is detected.
|
||||
"""
|
||||
detected: bool = Field(description="Whether a codebase was detected")
|
||||
dependency_files: Optional[List[DependencyFile]] = Field(
|
||||
default=None, description="List of detected dependency files"
|
||||
)
|
||||
|
||||
|
||||
class CodebaseSetupResponseCreatedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Codebase Setup Response Created event.
|
||||
This captures the user's choice to add a codebase (Y/N).
|
||||
"""
|
||||
user_consent_requested: bool = Field(
|
||||
description="Whether the user was asked for consent to add a codebase"
|
||||
)
|
||||
user_consent: Optional[bool] = Field(
|
||||
default=None, description="User's consent to add a codebase (True for yes, False for no, None if unknown)"
|
||||
)
|
||||
|
||||
|
||||
class CodebaseSetupCompletedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Codebase Setup Completed event.
|
||||
This is emitted when a codebase is successfully created or verified.
|
||||
"""
|
||||
is_created: bool = Field(description="Whether the codebase was created")
|
||||
codebase_id: Optional[str] = Field(default=None, description="ID of the codebase")
|
||||
|
||||
|
||||
class InitScanCompletedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Init Scan Completed event.
|
||||
This is emitted when the initial scan completes.
|
||||
"""
|
||||
scan_id: Optional[str] = Field(default=None, description="ID of the completed scan")
|
||||
|
||||
|
||||
class InitExitStep(str, Enum):
|
||||
"""
|
||||
Possible steps where the init process could be exited.
|
||||
"""
|
||||
PRE_AUTH = "pre_authentication"
|
||||
POST_AUTH = "post_authentication"
|
||||
PRE_FIREWALL_SETUP = "pre_firewall_setup"
|
||||
POST_FIREWALL_SETUP = "post_firewall_setup"
|
||||
PRE_CODEBASE_SETUP = "pre_codebase_setup"
|
||||
POST_CODEBASE_SETUP = "post_codebase_setup"
|
||||
PRE_SCAN = "pre_scan"
|
||||
POST_SCAN = "post_scan"
|
||||
COMPLETED = "completed"
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class InitExitedPayload(PayloadBase):
|
||||
"""
|
||||
Payload for the Init Exited event.
|
||||
This is emitted when the user exits the init process (e.g., via Ctrl+C).
|
||||
"""
|
||||
exit_step: InitExitStep = Field(
|
||||
description="The last step known before the user exited"
|
||||
)
|
||||
@@ -0,0 +1,187 @@
|
||||
from enum import Enum
|
||||
from functools import partial
|
||||
from typing import Any, Optional, Union
|
||||
from pydantic import BeforeValidator
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from .constants import CLI_SOURCE, GITHUB, ACTION, PYPI, DOCKER, APP
|
||||
|
||||
|
||||
class SourceType(str, Enum):
|
||||
"""
|
||||
Define the source types using URN format for product identification.
|
||||
"""
|
||||
|
||||
SAFETY_CLI_GITHUB_ACTION = f"{CLI_SOURCE}:{GITHUB}:{ACTION}"
|
||||
SAFETY_CLI_PYPI = f"{CLI_SOURCE}:{PYPI}"
|
||||
SAFETY_CLI_DOCKER = f"{CLI_SOURCE}:{DOCKER}"
|
||||
SAFETY_CLI_GITHUB_APP = f"{CLI_SOURCE}:{GITHUB}:{APP}"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""
|
||||
Return a human-readable description for this source type.
|
||||
"""
|
||||
descriptions = {
|
||||
self.SAFETY_CLI_GITHUB_ACTION: "Safety CLI via GitHub Action",
|
||||
self.SAFETY_CLI_PYPI: "Safety CLI via Python Package Index (PyPI)",
|
||||
self.SAFETY_CLI_DOCKER: "Safety CLI via Docker",
|
||||
self.SAFETY_CLI_GITHUB_APP: "Safety CLI via GitHub App",
|
||||
}
|
||||
return descriptions[self]
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
"""
|
||||
Return this Enum as choices format (value, display_name).
|
||||
"""
|
||||
return [(item.value, item.description) for item in cls]
|
||||
|
||||
|
||||
class EventTypeBase(str, Enum):
|
||||
"""
|
||||
Base class for all event types
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class EventType(EventTypeBase):
|
||||
"""
|
||||
Enumeration for different types of events.
|
||||
"""
|
||||
|
||||
COMMAND_ERROR = "com.safetycli.command.error"
|
||||
COMMAND_EXECUTED = "com.safetycli.command.executed"
|
||||
TOOL_COMMAND_EXECUTED = "com.safetycli.tool.command.executed"
|
||||
PACKAGE_INSTALLED = "com.safetycli.package.installed"
|
||||
PACKAGE_UPDATED = "com.safetycli.package.updated"
|
||||
PACKAGE_UNINSTALLED = "com.safetycli.package.uninstalled"
|
||||
PACKAGE_BLOCKED = "com.safetycli.package.blocked"
|
||||
FIREWALL_HEARTBEAT = "com.safetycli.firewall.heartbeat"
|
||||
FIREWALL_CONFIGURED = "com.safetycli.firewall.configured"
|
||||
FIREWALL_DISABLED = "com.safetycli.firewall.disabled"
|
||||
|
||||
INIT_STARTED = "com.safetycli.init.started"
|
||||
AUTH_STARTED = "com.safetycli.auth.started"
|
||||
AUTH_COMPLETED = "com.safetycli.auth.completed"
|
||||
FIREWALL_SETUP_RESPONSE_CREATED = "com.safetycli.firewall.setup.response.created"
|
||||
FIREWALL_SETUP_COMPLETED = "com.safetycli.firewall.setup.completed"
|
||||
CODEBASE_DETECTION_STATUS = "com.safetycli.codebase.detection.status"
|
||||
CODEBASE_SETUP_RESPONSE_CREATED = "com.safetycli.codebase.setup.response.created"
|
||||
CODEBASE_SETUP_COMPLETED = "com.safetycli.codebase.setup.completed"
|
||||
INIT_SCAN_COMPLETED = "com.safetycli.init.scan.completed"
|
||||
INIT_EXITED = "com.safetycli.init.exited"
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""
|
||||
Return a human-readable description for this event type.
|
||||
"""
|
||||
descriptions = {
|
||||
self.COMMAND_ERROR: "Command Error",
|
||||
self.COMMAND_EXECUTED: "Command Executed",
|
||||
self.TOOL_COMMAND_EXECUTED: "Tool Command Executed",
|
||||
self.PACKAGE_INSTALLED: "Package Installed",
|
||||
self.PACKAGE_UPDATED: "Package Updated",
|
||||
self.PACKAGE_UNINSTALLED: "Package Uninstalled",
|
||||
self.PACKAGE_BLOCKED: "Package Blocked",
|
||||
self.FIREWALL_HEARTBEAT: "Firewall Heartbeat",
|
||||
self.FIREWALL_CONFIGURED: "Firewall Configured",
|
||||
self.FIREWALL_DISABLED: "Firewall Disabled",
|
||||
self.INIT_STARTED: "Init Started",
|
||||
self.AUTH_STARTED: "Auth Started",
|
||||
self.AUTH_COMPLETED: "Auth Completed",
|
||||
self.FIREWALL_SETUP_RESPONSE_CREATED: "Firewall Setup Response Created",
|
||||
self.FIREWALL_SETUP_COMPLETED: "Firewall Setup Completed",
|
||||
self.CODEBASE_DETECTION_STATUS: "Codebase Detection Status",
|
||||
self.CODEBASE_SETUP_RESPONSE_CREATED: "Codebase Setup Response Created",
|
||||
self.CODEBASE_SETUP_COMPLETED: "Codebase Setup Completed",
|
||||
self.INIT_SCAN_COMPLETED: "Init Scan Completed",
|
||||
self.INIT_EXITED: "Init Exited",
|
||||
}
|
||||
return descriptions[self]
|
||||
|
||||
@classmethod
|
||||
def choices(cls):
|
||||
"""
|
||||
Return this Enum as choices format (value, display_name).
|
||||
"""
|
||||
return [(item.value, item.description) for item in cls]
|
||||
|
||||
|
||||
class ParamSource(str, Enum):
|
||||
"""
|
||||
Matches Click's parameter sources
|
||||
"""
|
||||
|
||||
COMMANDLINE = "commandline"
|
||||
ENVIRONMENT = "environment"
|
||||
CONFIG = "config"
|
||||
DEFAULT = "default"
|
||||
PROMPT = "prompt"
|
||||
|
||||
# Useful for tracking when we couldn't determine the source
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
class ToolType(str, Enum):
|
||||
"""
|
||||
Supported tools.
|
||||
"""
|
||||
|
||||
PIP = "pip"
|
||||
POETRY = "poetry"
|
||||
UV = "uv"
|
||||
CONDA = "conda"
|
||||
NPM = "npm"
|
||||
|
||||
|
||||
DEFAULT_MAX_BYTES: int = 32 * 1024 # 32 KB
|
||||
DEFAULT_ENCODING = "utf-8"
|
||||
|
||||
|
||||
def truncate_by_chars(
|
||||
value: Union[str, bytes, Any],
|
||||
max_chars: int,
|
||||
encoding: str = DEFAULT_ENCODING
|
||||
) -> str:
|
||||
"""
|
||||
Truncates a value to a maximum number of characters.
|
||||
"""
|
||||
# Convert to string if needed
|
||||
if isinstance(value, bytes):
|
||||
value = value.decode(encoding, errors="replace")
|
||||
elif not isinstance(value, str):
|
||||
value = str(value)
|
||||
|
||||
return value[:max_chars]
|
||||
|
||||
|
||||
def truncate_by_bytes(
|
||||
value: Union[str, bytes, Any],
|
||||
max_bytes: int,
|
||||
encoding: str = DEFAULT_ENCODING
|
||||
) -> str:
|
||||
"""
|
||||
Truncates a value to a maximum byte size.
|
||||
"""
|
||||
# Convert to bytes if needed
|
||||
if not isinstance(value, bytes):
|
||||
value = str(value).encode(encoding, errors="replace")
|
||||
|
||||
# Truncate and convert back to string
|
||||
return value[:max_bytes].decode(encoding, errors="ignore")
|
||||
|
||||
|
||||
StdOut = Annotated[
|
||||
str, BeforeValidator(partial(truncate_by_bytes, max_bytes=DEFAULT_MAX_BYTES))
|
||||
]
|
||||
StdErr = Annotated[
|
||||
str, BeforeValidator(partial(truncate_by_bytes, max_bytes=DEFAULT_MAX_BYTES))
|
||||
]
|
||||
StackTrace = Annotated[
|
||||
str, BeforeValidator(partial(truncate_by_bytes, max_bytes=DEFAULT_MAX_BYTES))
|
||||
]
|
||||
|
||||
LimitedStr = Annotated[str, BeforeValidator(partial(truncate_by_chars, max_chars=200))]
|
||||
Reference in New Issue
Block a user