diff --git a/.github/workflows/autogpt-ci.yml b/.github/workflows/autogpt-ci.yml index 9005d16077..2ce756a7ad 100644 --- a/.github/workflows/autogpt-ci.yml +++ b/.github/workflows/autogpt-ci.yml @@ -83,6 +83,15 @@ jobs: matrix: python-version: ["3.10"] + services: + minio: + image: minio/minio:edge-cicd + ports: + - 9000:9000 + options: > + --health-interval=10s --health-timeout=5s --health-retries=3 + --health-cmd="curl -f http://localhost:9000/minio/health/live" + steps: - name: Checkout repository uses: actions/checkout@v3 @@ -154,8 +163,11 @@ jobs: tests/unit tests/integration env: CI: true - OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} PLAIN_OUTPUT: True + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + S3_ENDPOINT_URL: http://localhost:9000 + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 diff --git a/.github/workflows/autogpt-docker-ci.yml b/.github/workflows/autogpt-docker-ci.yml index 4588f28690..dc555c3814 100644 --- a/.github/workflows/autogpt-docker-ci.yml +++ b/.github/workflows/autogpt-docker-ci.yml @@ -89,6 +89,15 @@ jobs: test: runs-on: ubuntu-latest timeout-minutes: 10 + + services: + minio: + image: minio/minio:edge-cicd + options: > + --name=minio + --health-interval=10s --health-timeout=5s --health-retries=3 + --health-cmd="curl -f http://localhost:9000/minio/health/live" + steps: - name: Check out repository uses: actions/checkout@v3 @@ -124,23 +133,25 @@ jobs: CI: true PLAIN_OUTPUT: True OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + S3_ENDPOINT_URL: http://minio:9000 + AWS_ACCESS_KEY_ID: minioadmin + AWS_SECRET_ACCESS_KEY: minioadmin run: | set +e - test_output=$( - docker run --env CI --env OPENAI_API_KEY \ - --entrypoint poetry ${{ env.IMAGE_NAME }} run \ - pytest -v --cov=autogpt --cov-branch --cov-report term-missing \ - --numprocesses=4 --durations=10 \ - tests/unit tests/integration 2>&1 - ) - test_failure=$? + docker run --env CI --env OPENAI_API_KEY \ + --network container:minio \ + --env S3_ENDPOINT_URL --env AWS_ACCESS_KEY_ID --env AWS_SECRET_ACCESS_KEY \ + --entrypoint poetry ${{ env.IMAGE_NAME }} run \ + pytest -v --cov=autogpt --cov-branch --cov-report term-missing \ + --numprocesses=4 --durations=10 \ + tests/unit tests/integration 2>&1 | tee test_output.txt - echo "$test_output" + test_failure=${PIPESTATUS[0]} cat << $EOF >> $GITHUB_STEP_SUMMARY # Tests $([ $test_failure = 0 ] && echo '✅' || echo '❌') \`\`\` - $test_output + $(cat test_output.txt) \`\`\` $EOF diff --git a/autogpts/autogpt/.env.template b/autogpts/autogpt/.env.template index 6e17575d23..886cfe4db9 100644 --- a/autogpts/autogpt/.env.template +++ b/autogpts/autogpt/.env.template @@ -8,9 +8,32 @@ OPENAI_API_KEY=your-openai-api-key ## EXECUTE_LOCAL_COMMANDS - Allow local command execution (Default: False) # EXECUTE_LOCAL_COMMANDS=False +### Workspace ### + ## RESTRICT_TO_WORKSPACE - Restrict file operations to workspace ./data/agents//workspace (Default: True) # RESTRICT_TO_WORKSPACE=True +## DISABLED_COMMAND_CATEGORIES - The list of categories of commands that are disabled (Default: None) +# DISABLED_COMMAND_CATEGORIES= + +## WORKSPACE_BACKEND - Choose a storage backend for workspace contents +## Options: local, gcs, s3 +# WORKSPACE_BACKEND=local + +## WORKSPACE_STORAGE_BUCKET - GCS/S3 Bucket to store workspace contents in +# WORKSPACE_STORAGE_BUCKET=autogpt + +## GCS Credentials +# see https://cloud.google.com/storage/docs/authentication#libauth + +## AWS/S3 Credentials +# see https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html + +## S3_ENDPOINT_URL - If you're using non-AWS S3, set your endpoint here. +# S3_ENDPOINT_URL= + +### Miscellaneous ### + ## USER_AGENT - Define the user-agent used by the requests library to browse website (string) # USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36" @@ -29,12 +52,6 @@ OPENAI_API_KEY=your-openai-api-key ## EXIT_KEY - Key to exit AutoGPT # EXIT_KEY=n -## PLAIN_OUTPUT - Plain output, which disables the spinner (Default: False) -# PLAIN_OUTPUT=False - -## DISABLED_COMMAND_CATEGORIES - The list of categories of commands that are disabled (Default: None) -# DISABLED_COMMAND_CATEGORIES= - ################################################################################ ### LLM PROVIDER ################################################################################ @@ -201,5 +218,5 @@ OPENAI_API_KEY=your-openai-api-key ## Note: Log file output is disabled if LOG_FORMAT=structured_google_cloud. # LOG_FILE_FORMAT=simple -## PLAIN_OUTPUT - Disables animated typing in the console output. +## PLAIN_OUTPUT - Disables animated typing and the spinner in the console output. (Default: False) # PLAIN_OUTPUT=False diff --git a/autogpts/autogpt/autogpt/agents/features/file_workspace.py b/autogpts/autogpt/autogpt/agents/features/file_workspace.py index ecdd287478..22ab8119d5 100644 --- a/autogpts/autogpt/autogpt/agents/features/file_workspace.py +++ b/autogpts/autogpt/autogpt/agents/features/file_workspace.py @@ -5,11 +5,15 @@ from typing import TYPE_CHECKING if TYPE_CHECKING: from pathlib import Path - from ..base import BaseAgent + from ..base import BaseAgent, Config -from autogpt.file_workspace import FileWorkspace +from autogpt.file_workspace import ( + FileWorkspace, + FileWorkspaceBackendName, + get_workspace, +) -from ..base import AgentFileManager, BaseAgentConfiguration +from ..base import AgentFileManager, BaseAgentSettings class FileWorkspaceMixin: @@ -22,32 +26,36 @@ class FileWorkspaceMixin: # Initialize other bases first, because we need the config from BaseAgent super(FileWorkspaceMixin, self).__init__(**kwargs) - config: BaseAgentConfiguration = getattr(self, "config") - if not isinstance(config, BaseAgentConfiguration): - raise ValueError( - "Cannot initialize Workspace for Agent without compatible .config" - ) file_manager: AgentFileManager = getattr(self, "file_manager") if not file_manager: return - self.workspace = _setup_workspace(file_manager, config) + self._setup_workspace() def attach_fs(self, agent_dir: Path): res = super(FileWorkspaceMixin, self).attach_fs(agent_dir) - self.workspace = _setup_workspace(self.file_manager, self.config) + self._setup_workspace() return res + def _setup_workspace(self) -> None: + settings: BaseAgentSettings = getattr(self, "state") + assert settings.agent_id, "Cannot attach workspace to anonymous agent" + app_config: Config = getattr(self, "legacy_config") + file_manager: AgentFileManager = getattr(self, "file_manager") -def _setup_workspace(file_manager: AgentFileManager, config: BaseAgentConfiguration): - workspace = FileWorkspace( - file_manager.root / "workspace", - restrict_to_root=not config.allow_fs_access, - ) - workspace.initialize() - return workspace + ws_backend = app_config.workspace_backend + local = ws_backend == FileWorkspaceBackendName.LOCAL + workspace = get_workspace( + backend=ws_backend, + id=settings.agent_id if not local else "", + root_path=file_manager.root / "workspace" if local else None, + ) + if local and settings.config.allow_fs_access: + workspace._restrict_to_root = False # type: ignore + workspace.initialize() + self.workspace = workspace def get_agent_workspace(agent: BaseAgent) -> FileWorkspace | None: diff --git a/autogpts/autogpt/autogpt/app/agent_protocol_server.py b/autogpts/autogpt/autogpt/app/agent_protocol_server.py index aaff9af7b6..2e620c1bd7 100644 --- a/autogpts/autogpt/autogpt/app/agent_protocol_server.py +++ b/autogpts/autogpt/autogpt/app/agent_protocol_server.py @@ -33,7 +33,11 @@ from autogpt.commands.system import finish from autogpt.commands.user_interaction import ask_user from autogpt.config import Config from autogpt.core.resource.model_providers import ChatModelProvider -from autogpt.file_workspace import FileWorkspace +from autogpt.file_workspace import ( + FileWorkspace, + FileWorkspaceBackendName, + get_workspace, +) from autogpt.models.action_history import ActionErrorResult, ActionSuccessResult logger = logging.getLogger(__name__) @@ -340,7 +344,7 @@ class AgentProtocolServer: else: file_path = os.path.join(relative_path, file_name) - workspace = get_task_agent_file_workspace(task_id, self.agent_manager) + workspace = self._get_task_agent_file_workspace(task_id, self.agent_manager) await workspace.write_file(file_path, data) artifact = await self.db.create_artifact( @@ -361,7 +365,7 @@ class AgentProtocolServer: file_path = os.path.join(artifact.relative_path, artifact.file_name) else: file_path = artifact.relative_path - workspace = get_task_agent_file_workspace(task_id, self.agent_manager) + workspace = self._get_task_agent_file_workspace(task_id, self.agent_manager) retrieved_artifact = workspace.read_file(file_path, binary=True) except NotFoundError: raise @@ -376,24 +380,33 @@ class AgentProtocolServer: }, ) + def _get_task_agent_file_workspace( + self, + task_id: str | int, + agent_manager: AgentManager, + ) -> FileWorkspace: + use_local_ws = ( + self.app_config.workspace_backend == FileWorkspaceBackendName.LOCAL + ) + agent_id = task_agent_id(task_id) + workspace = get_workspace( + backend=self.app_config.workspace_backend, + id=agent_id if not use_local_ws else "", + root_path=agent_manager.get_agent_dir( + agent_id=agent_id, + must_exist=True, + ) + / "workspace" + if use_local_ws + else None, + ) + workspace.initialize() + return workspace + def task_agent_id(task_id: str | int) -> str: return f"AutoGPT-{task_id}" -def get_task_agent_file_workspace( - task_id: str | int, - agent_manager: AgentManager, -) -> FileWorkspace: - return FileWorkspace( - root=agent_manager.get_agent_dir( - agent_id=task_agent_id(task_id), - must_exist=True, - ) - / "workspace", - restrict_to_root=True, - ) - - def fmt_kwargs(kwargs: dict) -> str: return ", ".join(f"{n}={repr(v)}" for n, v in kwargs.items()) diff --git a/autogpts/autogpt/autogpt/config/config.py b/autogpts/autogpt/autogpt/config/config.py index 1d3df46ca8..ff0053a760 100644 --- a/autogpts/autogpt/autogpt/config/config.py +++ b/autogpts/autogpt/autogpt/config/config.py @@ -20,6 +20,7 @@ from autogpt.core.resource.model_providers.openai import ( OPEN_AI_CHAT_MODELS, OpenAICredentials, ) +from autogpt.file_workspace import FileWorkspaceBackendName from autogpt.logs.config import LoggingConfig from autogpt.plugins.plugins_config import PluginsConfig from autogpt.speech import TTSConfig @@ -51,10 +52,19 @@ class Config(SystemSettings, arbitrary_types_allowed=True): chat_messages_enabled: bool = UserConfigurable( default=True, from_env=lambda: os.getenv("CHAT_MESSAGES_ENABLED") == "True" ) + # TTS configuration tts_config: TTSConfig = TTSConfig() logging: LoggingConfig = LoggingConfig() + # Workspace + workspace_backend: FileWorkspaceBackendName = UserConfigurable( + default=FileWorkspaceBackendName.LOCAL, + from_env=lambda: FileWorkspaceBackendName(v) + if (v := os.getenv("WORKSPACE_BACKEND")) + else None, + ) + ########################## # Agent Control Settings # ########################## diff --git a/autogpts/autogpt/autogpt/core/resource/model_providers/schema.py b/autogpts/autogpt/autogpt/core/resource/model_providers/schema.py index ccf3255b4d..46437a55e1 100644 --- a/autogpts/autogpt/autogpt/core/resource/model_providers/schema.py +++ b/autogpts/autogpt/autogpt/core/resource/model_providers/schema.py @@ -172,24 +172,10 @@ class ModelProviderCredentials(ProviderCredentials): api_version: SecretStr | None = UserConfigurable(default=None) deployment_id: SecretStr | None = UserConfigurable(default=None) - def unmasked(self) -> dict: - return unmask(self) - class Config: extra = "ignore" -def unmask(model: BaseModel): - unmasked_fields = {} - for field_name, field in model.__fields__.items(): - value = getattr(model, field_name) - if isinstance(value, SecretStr): - unmasked_fields[field_name] = value.get_secret_value() - else: - unmasked_fields[field_name] = value - return unmasked_fields - - class ModelProviderUsage(ProviderUsage): """Usage for a particular model from a model provider.""" diff --git a/autogpts/autogpt/autogpt/core/resource/schema.py b/autogpts/autogpt/autogpt/core/resource/schema.py index 0f97aedb03..ed7a94d029 100644 --- a/autogpts/autogpt/autogpt/core/resource/schema.py +++ b/autogpts/autogpt/autogpt/core/resource/schema.py @@ -1,7 +1,7 @@ import abc import enum -from pydantic import SecretBytes, SecretField, SecretStr +from pydantic import BaseModel, SecretBytes, SecretField, SecretStr from autogpt.core.configuration import ( SystemConfiguration, @@ -39,6 +39,9 @@ class ProviderBudget(SystemConfiguration): class ProviderCredentials(SystemConfiguration): """Struct for credentials.""" + def unmasked(self) -> dict: + return unmask(self) + class Config: json_encoders = { SecretStr: lambda v: v.get_secret_value() if v else None, @@ -47,6 +50,17 @@ class ProviderCredentials(SystemConfiguration): } +def unmask(model: BaseModel): + unmasked_fields = {} + for field_name, _ in model.__fields__.items(): + value = getattr(model, field_name) + if isinstance(value, SecretStr): + unmasked_fields[field_name] = value.get_secret_value() + else: + unmasked_fields[field_name] = value + return unmasked_fields + + class ProviderSettings(SystemSettings): resource_type: ResourceType credentials: ProviderCredentials | None = None diff --git a/autogpts/autogpt/autogpt/file_workspace/__init__.py b/autogpts/autogpt/autogpt/file_workspace/__init__.py index 76a26eefec..ebca767dd5 100644 --- a/autogpts/autogpt/autogpt/file_workspace/__init__.py +++ b/autogpts/autogpt/autogpt/file_workspace/__init__.py @@ -1,5 +1,46 @@ -from .file_workspace import FileWorkspace +import enum +from pathlib import Path +from typing import Optional + +from .base import FileWorkspace + + +class FileWorkspaceBackendName(str, enum.Enum): + LOCAL = "local" + GCS = "gcs" + S3 = "s3" + + +def get_workspace( + backend: FileWorkspaceBackendName, *, id: str = "", root_path: Optional[Path] = None +) -> FileWorkspace: + assert bool(root_path) != bool(id), "Specify root_path or id to get workspace" + if root_path is None: + root_path = Path(f"workspaces/{id}") + + match backend: + case FileWorkspaceBackendName.LOCAL: + from .local import FileWorkspaceConfiguration, LocalFileWorkspace + + config = FileWorkspaceConfiguration.from_env() + config.root = root_path + return LocalFileWorkspace(config) + case FileWorkspaceBackendName.S3: + from .s3 import S3FileWorkspace, S3FileWorkspaceConfiguration + + config = S3FileWorkspaceConfiguration.from_env() + config.root = root_path + return S3FileWorkspace(config) + case FileWorkspaceBackendName.GCS: + from .gcs import GCSFileWorkspace, GCSFileWorkspaceConfiguration + + config = GCSFileWorkspaceConfiguration.from_env() + config.root = root_path + return GCSFileWorkspace(config) + __all__ = [ "FileWorkspace", + "FileWorkspaceBackendName", + "get_workspace", ] diff --git a/autogpts/autogpt/autogpt/file_workspace/file_workspace.py b/autogpts/autogpt/autogpt/file_workspace/base.py similarity index 63% rename from autogpts/autogpt/autogpt/file_workspace/file_workspace.py rename to autogpts/autogpt/autogpt/file_workspace/base.py index 7a49c30136..c52162b29d 100644 --- a/autogpts/autogpt/autogpt/file_workspace/file_workspace.py +++ b/autogpts/autogpt/autogpt/file_workspace/base.py @@ -3,18 +3,23 @@ The FileWorkspace class provides an interface for interacting with a file worksp """ from __future__ import annotations -import inspect import logging +from abc import ABC, abstractmethod from pathlib import Path -from typing import Any, Callable, Optional +from typing import Any, Callable, Literal, Optional, overload + +from autogpt.core.configuration.schema import SystemConfiguration logger = logging.getLogger(__name__) -class FileWorkspace: - """A class that represents a file workspace.""" +class FileWorkspaceConfiguration(SystemConfiguration): + restrict_to_root: bool = True + root: Path = Path("/") - NULL_BYTES = ["\0", "\000", "\x00", "\u0000"] + +class FileWorkspace(ABC): + """A class that represents a file workspace.""" on_write_file: Callable[[Path], Any] | None = None """ @@ -24,22 +29,55 @@ class FileWorkspace: Path: The path of the file that was written, relative to the workspace root. """ - def __init__(self, root: str | Path, restrict_to_root: bool): - self._root = self._sanitize_path(root) - self._restrict_to_root = restrict_to_root - @property + @abstractmethod def root(self) -> Path: - """The root directory of the file workspace.""" - return self._root + """The root path of the file workspace.""" @property - def restrict_to_root(self): - """Whether to restrict generated paths to the root.""" - return self._restrict_to_root + @abstractmethod + def restrict_to_root(self) -> bool: + """Whether to restrict file access to within the workspace's root path.""" + @abstractmethod def initialize(self) -> None: - self.root.mkdir(exist_ok=True, parents=True) + """ + Calling `initialize()` should bring the workspace to a ready-to-use state. + For example, it can create the resource in which files will be stored, if it + doesn't exist yet. E.g. a folder on disk, or an S3 Bucket. + """ + + @abstractmethod + def open_file(self, path: str | Path, mode: str = "r"): + """Open a file in the workspace.""" + + @overload + @abstractmethod + def read_file(self, path: str | Path, binary: Literal[False] = False) -> str: + """Read a file in the workspace as text.""" + ... + + @overload + @abstractmethod + def read_file(self, path: str | Path, binary: Literal[True] = True) -> bytes: + """Read a file in the workspace as binary.""" + ... + + @abstractmethod + def read_file(self, path: str | Path, binary: bool = False) -> str | bytes: + """Read a file in the workspace.""" + + @abstractmethod + async def write_file(self, path: str | Path, content: str | bytes) -> None: + """Write to a file in the workspace.""" + + @abstractmethod + def list_files(self, path: str | Path = ".") -> list[Path]: + """List all files in a directory in the workspace.""" + + @abstractmethod + def delete_file(self, path: str | Path) -> None: + """Delete a file in the workspace.""" def get_path(self, relative_path: str | Path) -> Path: """Get the full path for an item in the workspace. @@ -50,44 +88,7 @@ class FileWorkspace: Returns: Path: The resolved path relative to the workspace. """ - return self._sanitize_path( - relative_path, - root=self.root, - restrict_to_root=self.restrict_to_root, - ) - - def open_file(self, path: str | Path, mode: str = "r"): - """Open a file in the workspace.""" - full_path = self.get_path(path) - return open(full_path, mode) - - def read_file(self, path: str | Path, binary: bool = False): - """Read a file in the workspace.""" - with self.open_file(path, "rb" if binary else "r") as file: - return file.read() - - async def write_file(self, path: str | Path, content: str | bytes): - """Write to a file in the workspace.""" - with self.open_file(path, "wb" if type(content) is bytes else "w") as file: - file.write(content) - - if self.on_write_file: - path = Path(path) - if path.is_absolute(): - path = path.relative_to(self.root) - res = self.on_write_file(path) - if inspect.isawaitable(res): - await res - - def list_files(self, path: str | Path = "."): - """List all files in a directory in the workspace.""" - full_path = self.get_path(path) - return [str(file) for file in full_path.glob("*") if file.is_file()] - - def delete_file(self, path: str | Path): - """Delete a file in the workspace.""" - full_path = self.get_path(path) - full_path.unlink() + return self._sanitize_path(relative_path, self.root) @staticmethod def _sanitize_path( @@ -113,9 +114,8 @@ class FileWorkspace: # Posix systems disallow null bytes in paths. Windows is agnostic about it. # Do an explicit check here for all sorts of null byte representations. - for null_byte in FileWorkspace.NULL_BYTES: - if null_byte in str(relative_path) or null_byte in str(root): - raise ValueError("embedded null byte") + if "\0" in str(relative_path) or "\0" in str(root): + raise ValueError("embedded null byte") if root is None: return Path(relative_path).resolve() diff --git a/autogpts/autogpt/autogpt/file_workspace/gcs.py b/autogpts/autogpt/autogpt/file_workspace/gcs.py new file mode 100644 index 0000000000..1be240873d --- /dev/null +++ b/autogpts/autogpt/autogpt/file_workspace/gcs.py @@ -0,0 +1,91 @@ +""" +The GCSWorkspace class provides an interface for interacting with a file workspace, and +stores the files in a Google Cloud Storage bucket. +""" +from __future__ import annotations + +import inspect +import logging +from pathlib import Path + +from google.cloud import storage + +from autogpt.core.configuration.schema import UserConfigurable + +from .base import FileWorkspace, FileWorkspaceConfiguration + +logger = logging.getLogger(__name__) + + +class GCSFileWorkspaceConfiguration(FileWorkspaceConfiguration): + bucket: str = UserConfigurable("autogpt", from_env="WORKSPACE_STORAGE_BUCKET") + + +class GCSFileWorkspace(FileWorkspace): + """A class that represents a Google Cloud Storage workspace.""" + + _bucket: storage.Bucket + + def __init__(self, config: GCSFileWorkspaceConfiguration): + self._bucket_name = config.bucket + self._root = config.root + + self._gcs = storage.Client() + super().__init__() + + @property + def root(self) -> Path: + """The root directory of the file workspace.""" + return self._root + + @property + def restrict_to_root(self): + """Whether to restrict generated paths to the root.""" + return True + + def initialize(self) -> None: + self._bucket = self._gcs.get_bucket(self._bucket_name) + + def get_path(self, relative_path: str | Path) -> Path: + return super().get_path(relative_path).relative_to(Path("/")) + + def open_file(self, path: str | Path, mode: str = "r"): + """Open a file in the workspace.""" + path = self.get_path(path) + blob = self._bucket.blob(str(path)) + return blob + + def read_file(self, path: str | Path, binary: bool = False) -> str | bytes: + """Read a file in the workspace.""" + blob = self.open_file(path, "r") + file_content = ( + blob.download_as_text() if not binary else blob.download_as_bytes() + ) + return file_content + + async def write_file(self, path: str | Path, content: str | bytes): + """Write to a file in the workspace.""" + blob = self.open_file(path, "w") + blob.upload_from_string(content) if isinstance( + content, str + ) else blob.upload_from_file(content) + + if self.on_write_file: + path = Path(path) + if path.is_absolute(): + path = path.relative_to(self.root) + res = self.on_write_file(path) + if inspect.isawaitable(res): + await res + + def list_files(self, path: str | Path = ".") -> list[Path]: + """List all files in a directory in the workspace.""" + path = self.get_path(path) + blobs = self._bucket.list_blobs(prefix=str(path)) + return [Path(blob.name) for blob in blobs if not blob.name.endswith("/")] + + def delete_file(self, path: str | Path) -> None: + """Delete a file in the workspace.""" + path = self.get_path(path) + blob = self._bucket.blob(str(path)) + blob.delete() diff --git a/autogpts/autogpt/autogpt/file_workspace/local.py b/autogpts/autogpt/autogpt/file_workspace/local.py new file mode 100644 index 0000000000..fd9fd3eb3d --- /dev/null +++ b/autogpts/autogpt/autogpt/file_workspace/local.py @@ -0,0 +1,67 @@ +""" +The LocalFileWorkspace class implements a FileWorkspace that works with local files. +""" +from __future__ import annotations + +import inspect +import logging +from pathlib import Path + +from .base import FileWorkspace, FileWorkspaceConfiguration + +logger = logging.getLogger(__name__) + + +class LocalFileWorkspace(FileWorkspace): + """A class that represents a file workspace.""" + + def __init__(self, config: FileWorkspaceConfiguration): + self._root = self._sanitize_path(config.root) + self._restrict_to_root = config.restrict_to_root + super().__init__() + + @property + def root(self) -> Path: + """The root directory of the file workspace.""" + return self._root + + @property + def restrict_to_root(self): + """Whether to restrict generated paths to the root.""" + return self._restrict_to_root + + def initialize(self) -> None: + self.root.mkdir(exist_ok=True, parents=True) + + def open_file(self, path: str | Path, mode: str = "r"): + """Open a file in the workspace.""" + full_path = self.get_path(path) + return open(full_path, mode) + + def read_file(self, path: str | Path, binary: bool = False): + """Read a file in the workspace.""" + with self.open_file(path, "rb" if binary else "r") as file: + return file.read() + + async def write_file(self, path: str | Path, content: str | bytes): + """Write to a file in the workspace.""" + with self.open_file(path, "wb" if type(content) is bytes else "w") as file: + file.write(content) + + if self.on_write_file: + path = Path(path) + if path.is_absolute(): + path = path.relative_to(self.root) + res = self.on_write_file(path) + if inspect.isawaitable(res): + await res + + def list_files(self, path: str | Path = "."): + """List all files in a directory in the workspace.""" + full_path = self.get_path(path) + return [str(file) for file in full_path.glob("*") if file.is_file()] + + def delete_file(self, path: str | Path): + """Delete a file in the workspace.""" + full_path = self.get_path(path) + full_path.unlink() diff --git a/autogpts/autogpt/autogpt/file_workspace/s3.py b/autogpts/autogpt/autogpt/file_workspace/s3.py new file mode 100644 index 0000000000..0b5fdb9c56 --- /dev/null +++ b/autogpts/autogpt/autogpt/file_workspace/s3.py @@ -0,0 +1,122 @@ +""" +The S3Workspace class provides an interface for interacting with a file workspace, and +stores the files in an S3 bucket. +""" +from __future__ import annotations + +import contextlib +import inspect +import logging +import os +from pathlib import Path +from typing import TYPE_CHECKING, Optional + +import boto3 +import botocore.exceptions +from pydantic import SecretStr + +from autogpt.core.configuration.schema import UserConfigurable + +from .base import FileWorkspace, FileWorkspaceConfiguration + +if TYPE_CHECKING: + import mypy_boto3_s3 + +logger = logging.getLogger(__name__) + + +class S3FileWorkspaceConfiguration(FileWorkspaceConfiguration): + bucket: str = UserConfigurable("autogpt", from_env="WORKSPACE_STORAGE_BUCKET") + s3_endpoint_url: Optional[SecretStr] = UserConfigurable( + from_env=lambda: SecretStr(v) if (v := os.getenv("S3_ENDPOINT_URL")) else None + ) + + +class S3FileWorkspace(FileWorkspace): + """A class that represents an S3 workspace.""" + + _bucket: mypy_boto3_s3.service_resource.Bucket + + def __init__(self, config: S3FileWorkspaceConfiguration): + self._bucket_name = config.bucket + self._root = config.root + + # https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html + self._s3 = boto3.resource( + "s3", + endpoint_url=config.s3_endpoint_url.get_secret_value() + if config.s3_endpoint_url + else None, + ) + + super().__init__() + + @property + def root(self) -> Path: + """The root directory of the file workspace.""" + return self._root + + @property + def restrict_to_root(self): + """Whether to restrict generated paths to the root.""" + return True + + def initialize(self) -> None: + try: + self._s3.meta.client.head_bucket(Bucket=self._bucket_name) + self._bucket = self._s3.Bucket(self._bucket_name) + except botocore.exceptions.ClientError as e: + if "(404)" not in str(e): + raise + self._bucket = self._s3.create_bucket(Bucket=self._bucket_name) + + def get_path(self, relative_path: str | Path) -> Path: + return super().get_path(relative_path).relative_to(Path("/")) + + def open_file(self, path: str | Path, mode: str = "r"): + """Open a file in the workspace.""" + path = self.get_path(path) + obj = self._bucket.Object(str(path)) + with contextlib.suppress(botocore.exceptions.ClientError): + obj.load() + return obj + + def read_file(self, path: str | Path, binary: bool = False) -> str | bytes: + """Read a file in the workspace.""" + file_content = self.open_file(path, "r").get()["Body"].read() + return file_content if binary else file_content.decode() + + async def write_file(self, path: str | Path, content: str | bytes): + """Write to a file in the workspace.""" + obj = self.open_file(path, "w") + obj.put(Body=content) + + if self.on_write_file: + path = Path(path) + if path.is_absolute(): + path = path.relative_to(self.root) + res = self.on_write_file(path) + if inspect.isawaitable(res): + await res + + def list_files(self, path: str | Path = ".") -> list[Path]: + """List all files in a directory in the workspace.""" + path = self.get_path(path) + if path == Path("."): + return [ + Path(obj.key) + for obj in self._bucket.objects.all() + if not obj.key.endswith("/") + ] + else: + return [ + Path(obj.key) + for obj in self._bucket.objects.filter(Prefix=str(path)) + if not obj.key.endswith("/") + ] + + def delete_file(self, path: str | Path) -> None: + """Delete a file in the workspace.""" + path = self.get_path(path) + obj = self._s3.Object(self._bucket_name, str(path)) + obj.delete() diff --git a/autogpts/autogpt/docker-compose.yml b/autogpts/autogpt/docker-compose.yml index 76284bd30e..281c8f6976 100644 --- a/autogpts/autogpt/docker-compose.yml +++ b/autogpts/autogpt/docker-compose.yml @@ -16,3 +16,34 @@ services: - ./docker-compose.yml:/app/docker-compose.yml:ro - ./Dockerfile:/app/Dockerfile:ro profiles: ["exclude-from-up"] + + # Only for TESTING purposes. Run with: docker compose run --build --rm autogpt-test + autogpt-test: + build: ./ + env_file: + - .env + environment: + S3_ENDPOINT_URL: http://minio:9000 + AWS_ACCESS_KEY_ID: minio + AWS_SECRET_ACCESS_KEY: minio123 + entrypoint: ["poetry", "run"] + command: ["pytest", "-v"] + volumes: + - ./autogpt:/app/autogpt + - ./tests:/app/tests + depends_on: + - minio + profiles: ["exclude-from-up"] + minio: + image: minio/minio + environment: + MINIO_ACCESS_KEY: minio + MINIO_SECRET_KEY: minio123 + ports: + - 9000:9000 + volumes: + - minio-data:/data + command: server /data + profiles: ["exclude-from-up"] +volumes: + minio-data: diff --git a/autogpts/autogpt/poetry.lock b/autogpts/autogpt/poetry.lock index 2551d05766..e4ae003b73 100644 --- a/autogpts/autogpt/poetry.lock +++ b/autogpts/autogpt/poetry.lock @@ -49,7 +49,7 @@ uvicorn = "^0.23.2" type = "git" url = "https://github.com/Significant-Gravitas/AutoGPT.git" reference = "HEAD" -resolved_reference = "0d91006e0bd77a3372e35ad8cf666b3f8eb2ce21" +resolved_reference = "c47f81a3e072a1914027aacb8677b0d2b35a533e" subdirectory = "benchmark" [[package]] @@ -330,7 +330,7 @@ benchmark = ["agbenchmark @ git+https://github.com/Significant-Gravitas/AutoGPT. type = "git" url = "https://github.com/Significant-Gravitas/AutoGPT.git" reference = "HEAD" -resolved_reference = "0d91006e0bd77a3372e35ad8cf666b3f8eb2ce21" +resolved_reference = "c47f81a3e072a1914027aacb8677b0d2b35a533e" subdirectory = "autogpts/forge" [[package]] @@ -480,6 +480,457 @@ files = [ [package.dependencies] numpy = {version = ">=1.19.0", markers = "python_version >= \"3.9\""} +[[package]] +name = "boto3" +version = "1.33.8" +description = "The AWS SDK for Python" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "boto3-1.33.8-py3-none-any.whl", hash = "sha256:b8c818125489fc0371ef28d806d36d8f1dcb71734fcb0d96b3201563e3e86f22"}, + {file = "boto3-1.33.8.tar.gz", hash = "sha256:d02a084b25aa8d46ef917b128e90877efab1ba45f9d1ba3a11f336930378e350"}, +] + +[package.dependencies] +botocore = ">=1.33.8,<1.34.0" +jmespath = ">=0.7.1,<2.0.0" +s3transfer = ">=0.8.2,<0.9.0" + +[package.extras] +crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] + +[[package]] +name = "boto3-stubs" +version = "1.33.8" +description = "Type annotations for boto3 1.33.8 generated with mypy-boto3-builder 7.21.0" +optional = false +python-versions = ">=3.7" +files = [ + {file = "boto3-stubs-1.33.8.tar.gz", hash = "sha256:e6706e75223b3230d3d7a739032a4eab595ffef1f8b5d905d0f5dfc5346823f6"}, + {file = "boto3_stubs-1.33.8-py3-none-any.whl", hash = "sha256:4ca63a7d1d84db1046bf36f895ee596367d67ae313de0ce05a4b2fa7ffea94d5"}, +] + +[package.dependencies] +botocore-stubs = "*" +mypy-boto3-s3 = {version = ">=1.33.0,<1.34.0", optional = true, markers = "extra == \"s3\""} +types-s3transfer = "*" +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + +[package.extras] +accessanalyzer = ["mypy-boto3-accessanalyzer (>=1.33.0,<1.34.0)"] +account = ["mypy-boto3-account (>=1.33.0,<1.34.0)"] +acm = ["mypy-boto3-acm (>=1.33.0,<1.34.0)"] +acm-pca = ["mypy-boto3-acm-pca (>=1.33.0,<1.34.0)"] +alexaforbusiness = ["mypy-boto3-alexaforbusiness (>=1.33.0,<1.34.0)"] +all = ["mypy-boto3-accessanalyzer (>=1.33.0,<1.34.0)", "mypy-boto3-account (>=1.33.0,<1.34.0)", "mypy-boto3-acm (>=1.33.0,<1.34.0)", "mypy-boto3-acm-pca (>=1.33.0,<1.34.0)", "mypy-boto3-alexaforbusiness (>=1.33.0,<1.34.0)", "mypy-boto3-amp (>=1.33.0,<1.34.0)", "mypy-boto3-amplify (>=1.33.0,<1.34.0)", "mypy-boto3-amplifybackend (>=1.33.0,<1.34.0)", "mypy-boto3-amplifyuibuilder (>=1.33.0,<1.34.0)", "mypy-boto3-apigateway (>=1.33.0,<1.34.0)", "mypy-boto3-apigatewaymanagementapi (>=1.33.0,<1.34.0)", "mypy-boto3-apigatewayv2 (>=1.33.0,<1.34.0)", "mypy-boto3-appconfig (>=1.33.0,<1.34.0)", "mypy-boto3-appconfigdata (>=1.33.0,<1.34.0)", "mypy-boto3-appfabric (>=1.33.0,<1.34.0)", "mypy-boto3-appflow (>=1.33.0,<1.34.0)", "mypy-boto3-appintegrations (>=1.33.0,<1.34.0)", "mypy-boto3-application-autoscaling (>=1.33.0,<1.34.0)", "mypy-boto3-application-insights (>=1.33.0,<1.34.0)", "mypy-boto3-applicationcostprofiler (>=1.33.0,<1.34.0)", "mypy-boto3-appmesh (>=1.33.0,<1.34.0)", "mypy-boto3-apprunner (>=1.33.0,<1.34.0)", "mypy-boto3-appstream (>=1.33.0,<1.34.0)", "mypy-boto3-appsync (>=1.33.0,<1.34.0)", "mypy-boto3-arc-zonal-shift (>=1.33.0,<1.34.0)", "mypy-boto3-athena (>=1.33.0,<1.34.0)", "mypy-boto3-auditmanager (>=1.33.0,<1.34.0)", "mypy-boto3-autoscaling (>=1.33.0,<1.34.0)", "mypy-boto3-autoscaling-plans (>=1.33.0,<1.34.0)", "mypy-boto3-b2bi (>=1.33.0,<1.34.0)", "mypy-boto3-backup (>=1.33.0,<1.34.0)", "mypy-boto3-backup-gateway (>=1.33.0,<1.34.0)", "mypy-boto3-backupstorage (>=1.33.0,<1.34.0)", "mypy-boto3-batch (>=1.33.0,<1.34.0)", "mypy-boto3-bcm-data-exports (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-agent (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-agent-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-bedrock-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-billingconductor (>=1.33.0,<1.34.0)", "mypy-boto3-braket (>=1.33.0,<1.34.0)", "mypy-boto3-budgets (>=1.33.0,<1.34.0)", "mypy-boto3-ce (>=1.33.0,<1.34.0)", "mypy-boto3-chime (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-identity (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-media-pipelines (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-meetings (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-messaging (>=1.33.0,<1.34.0)", "mypy-boto3-chime-sdk-voice (>=1.33.0,<1.34.0)", "mypy-boto3-cleanrooms (>=1.33.0,<1.34.0)", "mypy-boto3-cleanroomsml (>=1.33.0,<1.34.0)", "mypy-boto3-cloud9 (>=1.33.0,<1.34.0)", "mypy-boto3-cloudcontrol (>=1.33.0,<1.34.0)", "mypy-boto3-clouddirectory (>=1.33.0,<1.34.0)", "mypy-boto3-cloudformation (>=1.33.0,<1.34.0)", "mypy-boto3-cloudfront (>=1.33.0,<1.34.0)", "mypy-boto3-cloudfront-keyvaluestore (>=1.33.0,<1.34.0)", "mypy-boto3-cloudhsm (>=1.33.0,<1.34.0)", "mypy-boto3-cloudhsmv2 (>=1.33.0,<1.34.0)", "mypy-boto3-cloudsearch (>=1.33.0,<1.34.0)", "mypy-boto3-cloudsearchdomain (>=1.33.0,<1.34.0)", "mypy-boto3-cloudtrail (>=1.33.0,<1.34.0)", "mypy-boto3-cloudtrail-data (>=1.33.0,<1.34.0)", "mypy-boto3-cloudwatch (>=1.33.0,<1.34.0)", "mypy-boto3-codeartifact (>=1.33.0,<1.34.0)", "mypy-boto3-codebuild (>=1.33.0,<1.34.0)", "mypy-boto3-codecatalyst (>=1.33.0,<1.34.0)", "mypy-boto3-codecommit (>=1.33.0,<1.34.0)", "mypy-boto3-codedeploy (>=1.33.0,<1.34.0)", "mypy-boto3-codeguru-reviewer (>=1.33.0,<1.34.0)", "mypy-boto3-codeguru-security (>=1.33.0,<1.34.0)", "mypy-boto3-codeguruprofiler (>=1.33.0,<1.34.0)", "mypy-boto3-codepipeline (>=1.33.0,<1.34.0)", "mypy-boto3-codestar (>=1.33.0,<1.34.0)", "mypy-boto3-codestar-connections (>=1.33.0,<1.34.0)", "mypy-boto3-codestar-notifications (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-identity (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-idp (>=1.33.0,<1.34.0)", "mypy-boto3-cognito-sync (>=1.33.0,<1.34.0)", "mypy-boto3-comprehend (>=1.33.0,<1.34.0)", "mypy-boto3-comprehendmedical (>=1.33.0,<1.34.0)", "mypy-boto3-compute-optimizer (>=1.33.0,<1.34.0)", "mypy-boto3-config (>=1.33.0,<1.34.0)", "mypy-boto3-connect (>=1.33.0,<1.34.0)", "mypy-boto3-connect-contact-lens (>=1.33.0,<1.34.0)", "mypy-boto3-connectcampaigns (>=1.33.0,<1.34.0)", "mypy-boto3-connectcases (>=1.33.0,<1.34.0)", "mypy-boto3-connectparticipant (>=1.33.0,<1.34.0)", "mypy-boto3-controltower (>=1.33.0,<1.34.0)", "mypy-boto3-cost-optimization-hub (>=1.33.0,<1.34.0)", "mypy-boto3-cur (>=1.33.0,<1.34.0)", "mypy-boto3-customer-profiles (>=1.33.0,<1.34.0)", "mypy-boto3-databrew (>=1.33.0,<1.34.0)", "mypy-boto3-dataexchange (>=1.33.0,<1.34.0)", "mypy-boto3-datapipeline (>=1.33.0,<1.34.0)", "mypy-boto3-datasync (>=1.33.0,<1.34.0)", "mypy-boto3-datazone (>=1.33.0,<1.34.0)", "mypy-boto3-dax (>=1.33.0,<1.34.0)", "mypy-boto3-detective (>=1.33.0,<1.34.0)", "mypy-boto3-devicefarm (>=1.33.0,<1.34.0)", "mypy-boto3-devops-guru (>=1.33.0,<1.34.0)", "mypy-boto3-directconnect (>=1.33.0,<1.34.0)", "mypy-boto3-discovery (>=1.33.0,<1.34.0)", "mypy-boto3-dlm (>=1.33.0,<1.34.0)", "mypy-boto3-dms (>=1.33.0,<1.34.0)", "mypy-boto3-docdb (>=1.33.0,<1.34.0)", "mypy-boto3-docdb-elastic (>=1.33.0,<1.34.0)", "mypy-boto3-drs (>=1.33.0,<1.34.0)", "mypy-boto3-ds (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodb (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodbstreams (>=1.33.0,<1.34.0)", "mypy-boto3-ebs (>=1.33.0,<1.34.0)", "mypy-boto3-ec2 (>=1.33.0,<1.34.0)", "mypy-boto3-ec2-instance-connect (>=1.33.0,<1.34.0)", "mypy-boto3-ecr (>=1.33.0,<1.34.0)", "mypy-boto3-ecr-public (>=1.33.0,<1.34.0)", "mypy-boto3-ecs (>=1.33.0,<1.34.0)", "mypy-boto3-efs (>=1.33.0,<1.34.0)", "mypy-boto3-eks (>=1.33.0,<1.34.0)", "mypy-boto3-eks-auth (>=1.33.0,<1.34.0)", "mypy-boto3-elastic-inference (>=1.33.0,<1.34.0)", "mypy-boto3-elasticache (>=1.33.0,<1.34.0)", "mypy-boto3-elasticbeanstalk (>=1.33.0,<1.34.0)", "mypy-boto3-elastictranscoder (>=1.33.0,<1.34.0)", "mypy-boto3-elb (>=1.33.0,<1.34.0)", "mypy-boto3-elbv2 (>=1.33.0,<1.34.0)", "mypy-boto3-emr (>=1.33.0,<1.34.0)", "mypy-boto3-emr-containers (>=1.33.0,<1.34.0)", "mypy-boto3-emr-serverless (>=1.33.0,<1.34.0)", "mypy-boto3-entityresolution (>=1.33.0,<1.34.0)", "mypy-boto3-es (>=1.33.0,<1.34.0)", "mypy-boto3-events (>=1.33.0,<1.34.0)", "mypy-boto3-evidently (>=1.33.0,<1.34.0)", "mypy-boto3-finspace (>=1.33.0,<1.34.0)", "mypy-boto3-finspace-data (>=1.33.0,<1.34.0)", "mypy-boto3-firehose (>=1.33.0,<1.34.0)", "mypy-boto3-fis (>=1.33.0,<1.34.0)", "mypy-boto3-fms (>=1.33.0,<1.34.0)", "mypy-boto3-forecast (>=1.33.0,<1.34.0)", "mypy-boto3-forecastquery (>=1.33.0,<1.34.0)", "mypy-boto3-frauddetector (>=1.33.0,<1.34.0)", "mypy-boto3-freetier (>=1.33.0,<1.34.0)", "mypy-boto3-fsx (>=1.33.0,<1.34.0)", "mypy-boto3-gamelift (>=1.33.0,<1.34.0)", "mypy-boto3-glacier (>=1.33.0,<1.34.0)", "mypy-boto3-globalaccelerator (>=1.33.0,<1.34.0)", "mypy-boto3-glue (>=1.33.0,<1.34.0)", "mypy-boto3-grafana (>=1.33.0,<1.34.0)", "mypy-boto3-greengrass (>=1.33.0,<1.34.0)", "mypy-boto3-greengrassv2 (>=1.33.0,<1.34.0)", "mypy-boto3-groundstation (>=1.33.0,<1.34.0)", "mypy-boto3-guardduty (>=1.33.0,<1.34.0)", "mypy-boto3-health (>=1.33.0,<1.34.0)", "mypy-boto3-healthlake (>=1.33.0,<1.34.0)", "mypy-boto3-honeycode (>=1.33.0,<1.34.0)", "mypy-boto3-iam (>=1.33.0,<1.34.0)", "mypy-boto3-identitystore (>=1.33.0,<1.34.0)", "mypy-boto3-imagebuilder (>=1.33.0,<1.34.0)", "mypy-boto3-importexport (>=1.33.0,<1.34.0)", "mypy-boto3-inspector (>=1.33.0,<1.34.0)", "mypy-boto3-inspector-scan (>=1.33.0,<1.34.0)", "mypy-boto3-inspector2 (>=1.33.0,<1.34.0)", "mypy-boto3-internetmonitor (>=1.33.0,<1.34.0)", "mypy-boto3-iot (>=1.33.0,<1.34.0)", "mypy-boto3-iot-data (>=1.33.0,<1.34.0)", "mypy-boto3-iot-jobs-data (>=1.33.0,<1.34.0)", "mypy-boto3-iot-roborunner (>=1.33.0,<1.34.0)", "mypy-boto3-iot1click-devices (>=1.33.0,<1.34.0)", "mypy-boto3-iot1click-projects (>=1.33.0,<1.34.0)", "mypy-boto3-iotanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-iotdeviceadvisor (>=1.33.0,<1.34.0)", "mypy-boto3-iotevents (>=1.33.0,<1.34.0)", "mypy-boto3-iotevents-data (>=1.33.0,<1.34.0)", "mypy-boto3-iotfleethub (>=1.33.0,<1.34.0)", "mypy-boto3-iotfleetwise (>=1.33.0,<1.34.0)", "mypy-boto3-iotsecuretunneling (>=1.33.0,<1.34.0)", "mypy-boto3-iotsitewise (>=1.33.0,<1.34.0)", "mypy-boto3-iotthingsgraph (>=1.33.0,<1.34.0)", "mypy-boto3-iottwinmaker (>=1.33.0,<1.34.0)", "mypy-boto3-iotwireless (>=1.33.0,<1.34.0)", "mypy-boto3-ivs (>=1.33.0,<1.34.0)", "mypy-boto3-ivs-realtime (>=1.33.0,<1.34.0)", "mypy-boto3-ivschat (>=1.33.0,<1.34.0)", "mypy-boto3-kafka (>=1.33.0,<1.34.0)", "mypy-boto3-kafkaconnect (>=1.33.0,<1.34.0)", "mypy-boto3-kendra (>=1.33.0,<1.34.0)", "mypy-boto3-kendra-ranking (>=1.33.0,<1.34.0)", "mypy-boto3-keyspaces (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-archived-media (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-media (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-signaling (>=1.33.0,<1.34.0)", "mypy-boto3-kinesis-video-webrtc-storage (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisanalyticsv2 (>=1.33.0,<1.34.0)", "mypy-boto3-kinesisvideo (>=1.33.0,<1.34.0)", "mypy-boto3-kms (>=1.33.0,<1.34.0)", "mypy-boto3-lakeformation (>=1.33.0,<1.34.0)", "mypy-boto3-lambda (>=1.33.0,<1.34.0)", "mypy-boto3-launch-wizard (>=1.33.0,<1.34.0)", "mypy-boto3-lex-models (>=1.33.0,<1.34.0)", "mypy-boto3-lex-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-lexv2-models (>=1.33.0,<1.34.0)", "mypy-boto3-lexv2-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager-linux-subscriptions (>=1.33.0,<1.34.0)", "mypy-boto3-license-manager-user-subscriptions (>=1.33.0,<1.34.0)", "mypy-boto3-lightsail (>=1.33.0,<1.34.0)", "mypy-boto3-location (>=1.33.0,<1.34.0)", "mypy-boto3-logs (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutequipment (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutmetrics (>=1.33.0,<1.34.0)", "mypy-boto3-lookoutvision (>=1.33.0,<1.34.0)", "mypy-boto3-m2 (>=1.33.0,<1.34.0)", "mypy-boto3-machinelearning (>=1.33.0,<1.34.0)", "mypy-boto3-macie2 (>=1.33.0,<1.34.0)", "mypy-boto3-managedblockchain (>=1.33.0,<1.34.0)", "mypy-boto3-managedblockchain-query (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-agreement (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-catalog (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-deployment (>=1.33.0,<1.34.0)", "mypy-boto3-marketplace-entitlement (>=1.33.0,<1.34.0)", "mypy-boto3-marketplacecommerceanalytics (>=1.33.0,<1.34.0)", "mypy-boto3-mediaconnect (>=1.33.0,<1.34.0)", "mypy-boto3-mediaconvert (>=1.33.0,<1.34.0)", "mypy-boto3-medialive (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackage (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackage-vod (>=1.33.0,<1.34.0)", "mypy-boto3-mediapackagev2 (>=1.33.0,<1.34.0)", "mypy-boto3-mediastore (>=1.33.0,<1.34.0)", "mypy-boto3-mediastore-data (>=1.33.0,<1.34.0)", "mypy-boto3-mediatailor (>=1.33.0,<1.34.0)", "mypy-boto3-medical-imaging (>=1.33.0,<1.34.0)", "mypy-boto3-memorydb (>=1.33.0,<1.34.0)", "mypy-boto3-meteringmarketplace (>=1.33.0,<1.34.0)", "mypy-boto3-mgh (>=1.33.0,<1.34.0)", "mypy-boto3-mgn (>=1.33.0,<1.34.0)", "mypy-boto3-migration-hub-refactor-spaces (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhub-config (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhuborchestrator (>=1.33.0,<1.34.0)", "mypy-boto3-migrationhubstrategy (>=1.33.0,<1.34.0)", "mypy-boto3-mobile (>=1.33.0,<1.34.0)", "mypy-boto3-mq (>=1.33.0,<1.34.0)", "mypy-boto3-mturk (>=1.33.0,<1.34.0)", "mypy-boto3-mwaa (>=1.33.0,<1.34.0)", "mypy-boto3-neptune (>=1.33.0,<1.34.0)", "mypy-boto3-neptunedata (>=1.33.0,<1.34.0)", "mypy-boto3-network-firewall (>=1.33.0,<1.34.0)", "mypy-boto3-networkmanager (>=1.33.0,<1.34.0)", "mypy-boto3-nimble (>=1.33.0,<1.34.0)", "mypy-boto3-oam (>=1.33.0,<1.34.0)", "mypy-boto3-omics (>=1.33.0,<1.34.0)", "mypy-boto3-opensearch (>=1.33.0,<1.34.0)", "mypy-boto3-opensearchserverless (>=1.33.0,<1.34.0)", "mypy-boto3-opsworks (>=1.33.0,<1.34.0)", "mypy-boto3-opsworkscm (>=1.33.0,<1.34.0)", "mypy-boto3-organizations (>=1.33.0,<1.34.0)", "mypy-boto3-osis (>=1.33.0,<1.34.0)", "mypy-boto3-outposts (>=1.33.0,<1.34.0)", "mypy-boto3-panorama (>=1.33.0,<1.34.0)", "mypy-boto3-payment-cryptography (>=1.33.0,<1.34.0)", "mypy-boto3-payment-cryptography-data (>=1.33.0,<1.34.0)", "mypy-boto3-pca-connector-ad (>=1.33.0,<1.34.0)", "mypy-boto3-personalize (>=1.33.0,<1.34.0)", "mypy-boto3-personalize-events (>=1.33.0,<1.34.0)", "mypy-boto3-personalize-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-pi (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-email (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-sms-voice (>=1.33.0,<1.34.0)", "mypy-boto3-pinpoint-sms-voice-v2 (>=1.33.0,<1.34.0)", "mypy-boto3-pipes (>=1.33.0,<1.34.0)", "mypy-boto3-polly (>=1.33.0,<1.34.0)", "mypy-boto3-pricing (>=1.33.0,<1.34.0)", "mypy-boto3-privatenetworks (>=1.33.0,<1.34.0)", "mypy-boto3-proton (>=1.33.0,<1.34.0)", "mypy-boto3-qbusiness (>=1.33.0,<1.34.0)", "mypy-boto3-qconnect (>=1.33.0,<1.34.0)", "mypy-boto3-qldb (>=1.33.0,<1.34.0)", "mypy-boto3-qldb-session (>=1.33.0,<1.34.0)", "mypy-boto3-quicksight (>=1.33.0,<1.34.0)", "mypy-boto3-ram (>=1.33.0,<1.34.0)", "mypy-boto3-rbin (>=1.33.0,<1.34.0)", "mypy-boto3-rds (>=1.33.0,<1.34.0)", "mypy-boto3-rds-data (>=1.33.0,<1.34.0)", "mypy-boto3-redshift (>=1.33.0,<1.34.0)", "mypy-boto3-redshift-data (>=1.33.0,<1.34.0)", "mypy-boto3-redshift-serverless (>=1.33.0,<1.34.0)", "mypy-boto3-rekognition (>=1.33.0,<1.34.0)", "mypy-boto3-repostspace (>=1.33.0,<1.34.0)", "mypy-boto3-resiliencehub (>=1.33.0,<1.34.0)", "mypy-boto3-resource-explorer-2 (>=1.33.0,<1.34.0)", "mypy-boto3-resource-groups (>=1.33.0,<1.34.0)", "mypy-boto3-resourcegroupstaggingapi (>=1.33.0,<1.34.0)", "mypy-boto3-robomaker (>=1.33.0,<1.34.0)", "mypy-boto3-rolesanywhere (>=1.33.0,<1.34.0)", "mypy-boto3-route53 (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-cluster (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-control-config (>=1.33.0,<1.34.0)", "mypy-boto3-route53-recovery-readiness (>=1.33.0,<1.34.0)", "mypy-boto3-route53domains (>=1.33.0,<1.34.0)", "mypy-boto3-route53resolver (>=1.33.0,<1.34.0)", "mypy-boto3-rum (>=1.33.0,<1.34.0)", "mypy-boto3-s3 (>=1.33.0,<1.34.0)", "mypy-boto3-s3control (>=1.33.0,<1.34.0)", "mypy-boto3-s3outposts (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-a2i-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-edge (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-featurestore-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-geospatial (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-metrics (>=1.33.0,<1.34.0)", "mypy-boto3-sagemaker-runtime (>=1.33.0,<1.34.0)", "mypy-boto3-savingsplans (>=1.33.0,<1.34.0)", "mypy-boto3-scheduler (>=1.33.0,<1.34.0)", "mypy-boto3-schemas (>=1.33.0,<1.34.0)", "mypy-boto3-sdb (>=1.33.0,<1.34.0)", "mypy-boto3-secretsmanager (>=1.33.0,<1.34.0)", "mypy-boto3-securityhub (>=1.33.0,<1.34.0)", "mypy-boto3-securitylake (>=1.33.0,<1.34.0)", "mypy-boto3-serverlessrepo (>=1.33.0,<1.34.0)", "mypy-boto3-service-quotas (>=1.33.0,<1.34.0)", "mypy-boto3-servicecatalog (>=1.33.0,<1.34.0)", "mypy-boto3-servicecatalog-appregistry (>=1.33.0,<1.34.0)", "mypy-boto3-servicediscovery (>=1.33.0,<1.34.0)", "mypy-boto3-ses (>=1.33.0,<1.34.0)", "mypy-boto3-sesv2 (>=1.33.0,<1.34.0)", "mypy-boto3-shield (>=1.33.0,<1.34.0)", "mypy-boto3-signer (>=1.33.0,<1.34.0)", "mypy-boto3-simspaceweaver (>=1.33.0,<1.34.0)", "mypy-boto3-sms (>=1.33.0,<1.34.0)", "mypy-boto3-sms-voice (>=1.33.0,<1.34.0)", "mypy-boto3-snow-device-management (>=1.33.0,<1.34.0)", "mypy-boto3-snowball (>=1.33.0,<1.34.0)", "mypy-boto3-sns (>=1.33.0,<1.34.0)", "mypy-boto3-sqs (>=1.33.0,<1.34.0)", "mypy-boto3-ssm (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-contacts (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-incidents (>=1.33.0,<1.34.0)", "mypy-boto3-ssm-sap (>=1.33.0,<1.34.0)", "mypy-boto3-sso (>=1.33.0,<1.34.0)", "mypy-boto3-sso-admin (>=1.33.0,<1.34.0)", "mypy-boto3-sso-oidc (>=1.33.0,<1.34.0)", "mypy-boto3-stepfunctions (>=1.33.0,<1.34.0)", "mypy-boto3-storagegateway (>=1.33.0,<1.34.0)", "mypy-boto3-sts (>=1.33.0,<1.34.0)", "mypy-boto3-support (>=1.33.0,<1.34.0)", "mypy-boto3-support-app (>=1.33.0,<1.34.0)", "mypy-boto3-swf (>=1.33.0,<1.34.0)", "mypy-boto3-synthetics (>=1.33.0,<1.34.0)", "mypy-boto3-textract (>=1.33.0,<1.34.0)", "mypy-boto3-timestream-query (>=1.33.0,<1.34.0)", "mypy-boto3-timestream-write (>=1.33.0,<1.34.0)", "mypy-boto3-tnb (>=1.33.0,<1.34.0)", "mypy-boto3-transcribe (>=1.33.0,<1.34.0)", "mypy-boto3-transfer (>=1.33.0,<1.34.0)", "mypy-boto3-translate (>=1.33.0,<1.34.0)", "mypy-boto3-trustedadvisor (>=1.33.0,<1.34.0)", "mypy-boto3-verifiedpermissions (>=1.33.0,<1.34.0)", "mypy-boto3-voice-id (>=1.33.0,<1.34.0)", "mypy-boto3-vpc-lattice (>=1.33.0,<1.34.0)", "mypy-boto3-waf (>=1.33.0,<1.34.0)", "mypy-boto3-waf-regional (>=1.33.0,<1.34.0)", "mypy-boto3-wafv2 (>=1.33.0,<1.34.0)", "mypy-boto3-wellarchitected (>=1.33.0,<1.34.0)", "mypy-boto3-wisdom (>=1.33.0,<1.34.0)", "mypy-boto3-workdocs (>=1.33.0,<1.34.0)", "mypy-boto3-worklink (>=1.33.0,<1.34.0)", "mypy-boto3-workmail (>=1.33.0,<1.34.0)", "mypy-boto3-workmailmessageflow (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces-thin-client (>=1.33.0,<1.34.0)", "mypy-boto3-workspaces-web (>=1.33.0,<1.34.0)", "mypy-boto3-xray (>=1.33.0,<1.34.0)"] +amp = ["mypy-boto3-amp (>=1.33.0,<1.34.0)"] +amplify = ["mypy-boto3-amplify (>=1.33.0,<1.34.0)"] +amplifybackend = ["mypy-boto3-amplifybackend (>=1.33.0,<1.34.0)"] +amplifyuibuilder = ["mypy-boto3-amplifyuibuilder (>=1.33.0,<1.34.0)"] +apigateway = ["mypy-boto3-apigateway (>=1.33.0,<1.34.0)"] +apigatewaymanagementapi = ["mypy-boto3-apigatewaymanagementapi (>=1.33.0,<1.34.0)"] +apigatewayv2 = ["mypy-boto3-apigatewayv2 (>=1.33.0,<1.34.0)"] +appconfig = ["mypy-boto3-appconfig (>=1.33.0,<1.34.0)"] +appconfigdata = ["mypy-boto3-appconfigdata (>=1.33.0,<1.34.0)"] +appfabric = ["mypy-boto3-appfabric (>=1.33.0,<1.34.0)"] +appflow = ["mypy-boto3-appflow (>=1.33.0,<1.34.0)"] +appintegrations = ["mypy-boto3-appintegrations (>=1.33.0,<1.34.0)"] +application-autoscaling = ["mypy-boto3-application-autoscaling (>=1.33.0,<1.34.0)"] +application-insights = ["mypy-boto3-application-insights (>=1.33.0,<1.34.0)"] +applicationcostprofiler = ["mypy-boto3-applicationcostprofiler (>=1.33.0,<1.34.0)"] +appmesh = ["mypy-boto3-appmesh (>=1.33.0,<1.34.0)"] +apprunner = ["mypy-boto3-apprunner (>=1.33.0,<1.34.0)"] +appstream = ["mypy-boto3-appstream (>=1.33.0,<1.34.0)"] +appsync = ["mypy-boto3-appsync (>=1.33.0,<1.34.0)"] +arc-zonal-shift = ["mypy-boto3-arc-zonal-shift (>=1.33.0,<1.34.0)"] +athena = ["mypy-boto3-athena (>=1.33.0,<1.34.0)"] +auditmanager = ["mypy-boto3-auditmanager (>=1.33.0,<1.34.0)"] +autoscaling = ["mypy-boto3-autoscaling (>=1.33.0,<1.34.0)"] +autoscaling-plans = ["mypy-boto3-autoscaling-plans (>=1.33.0,<1.34.0)"] +b2bi = ["mypy-boto3-b2bi (>=1.33.0,<1.34.0)"] +backup = ["mypy-boto3-backup (>=1.33.0,<1.34.0)"] +backup-gateway = ["mypy-boto3-backup-gateway (>=1.33.0,<1.34.0)"] +backupstorage = ["mypy-boto3-backupstorage (>=1.33.0,<1.34.0)"] +batch = ["mypy-boto3-batch (>=1.33.0,<1.34.0)"] +bcm-data-exports = ["mypy-boto3-bcm-data-exports (>=1.33.0,<1.34.0)"] +bedrock = ["mypy-boto3-bedrock (>=1.33.0,<1.34.0)"] +bedrock-agent = ["mypy-boto3-bedrock-agent (>=1.33.0,<1.34.0)"] +bedrock-agent-runtime = ["mypy-boto3-bedrock-agent-runtime (>=1.33.0,<1.34.0)"] +bedrock-runtime = ["mypy-boto3-bedrock-runtime (>=1.33.0,<1.34.0)"] +billingconductor = ["mypy-boto3-billingconductor (>=1.33.0,<1.34.0)"] +boto3 = ["boto3 (==1.33.8)", "botocore (==1.33.8)"] +braket = ["mypy-boto3-braket (>=1.33.0,<1.34.0)"] +budgets = ["mypy-boto3-budgets (>=1.33.0,<1.34.0)"] +ce = ["mypy-boto3-ce (>=1.33.0,<1.34.0)"] +chime = ["mypy-boto3-chime (>=1.33.0,<1.34.0)"] +chime-sdk-identity = ["mypy-boto3-chime-sdk-identity (>=1.33.0,<1.34.0)"] +chime-sdk-media-pipelines = ["mypy-boto3-chime-sdk-media-pipelines (>=1.33.0,<1.34.0)"] +chime-sdk-meetings = ["mypy-boto3-chime-sdk-meetings (>=1.33.0,<1.34.0)"] +chime-sdk-messaging = ["mypy-boto3-chime-sdk-messaging (>=1.33.0,<1.34.0)"] +chime-sdk-voice = ["mypy-boto3-chime-sdk-voice (>=1.33.0,<1.34.0)"] +cleanrooms = ["mypy-boto3-cleanrooms (>=1.33.0,<1.34.0)"] +cleanroomsml = ["mypy-boto3-cleanroomsml (>=1.33.0,<1.34.0)"] +cloud9 = ["mypy-boto3-cloud9 (>=1.33.0,<1.34.0)"] +cloudcontrol = ["mypy-boto3-cloudcontrol (>=1.33.0,<1.34.0)"] +clouddirectory = ["mypy-boto3-clouddirectory (>=1.33.0,<1.34.0)"] +cloudformation = ["mypy-boto3-cloudformation (>=1.33.0,<1.34.0)"] +cloudfront = ["mypy-boto3-cloudfront (>=1.33.0,<1.34.0)"] +cloudfront-keyvaluestore = ["mypy-boto3-cloudfront-keyvaluestore (>=1.33.0,<1.34.0)"] +cloudhsm = ["mypy-boto3-cloudhsm (>=1.33.0,<1.34.0)"] +cloudhsmv2 = ["mypy-boto3-cloudhsmv2 (>=1.33.0,<1.34.0)"] +cloudsearch = ["mypy-boto3-cloudsearch (>=1.33.0,<1.34.0)"] +cloudsearchdomain = ["mypy-boto3-cloudsearchdomain (>=1.33.0,<1.34.0)"] +cloudtrail = ["mypy-boto3-cloudtrail (>=1.33.0,<1.34.0)"] +cloudtrail-data = ["mypy-boto3-cloudtrail-data (>=1.33.0,<1.34.0)"] +cloudwatch = ["mypy-boto3-cloudwatch (>=1.33.0,<1.34.0)"] +codeartifact = ["mypy-boto3-codeartifact (>=1.33.0,<1.34.0)"] +codebuild = ["mypy-boto3-codebuild (>=1.33.0,<1.34.0)"] +codecatalyst = ["mypy-boto3-codecatalyst (>=1.33.0,<1.34.0)"] +codecommit = ["mypy-boto3-codecommit (>=1.33.0,<1.34.0)"] +codedeploy = ["mypy-boto3-codedeploy (>=1.33.0,<1.34.0)"] +codeguru-reviewer = ["mypy-boto3-codeguru-reviewer (>=1.33.0,<1.34.0)"] +codeguru-security = ["mypy-boto3-codeguru-security (>=1.33.0,<1.34.0)"] +codeguruprofiler = ["mypy-boto3-codeguruprofiler (>=1.33.0,<1.34.0)"] +codepipeline = ["mypy-boto3-codepipeline (>=1.33.0,<1.34.0)"] +codestar = ["mypy-boto3-codestar (>=1.33.0,<1.34.0)"] +codestar-connections = ["mypy-boto3-codestar-connections (>=1.33.0,<1.34.0)"] +codestar-notifications = ["mypy-boto3-codestar-notifications (>=1.33.0,<1.34.0)"] +cognito-identity = ["mypy-boto3-cognito-identity (>=1.33.0,<1.34.0)"] +cognito-idp = ["mypy-boto3-cognito-idp (>=1.33.0,<1.34.0)"] +cognito-sync = ["mypy-boto3-cognito-sync (>=1.33.0,<1.34.0)"] +comprehend = ["mypy-boto3-comprehend (>=1.33.0,<1.34.0)"] +comprehendmedical = ["mypy-boto3-comprehendmedical (>=1.33.0,<1.34.0)"] +compute-optimizer = ["mypy-boto3-compute-optimizer (>=1.33.0,<1.34.0)"] +config = ["mypy-boto3-config (>=1.33.0,<1.34.0)"] +connect = ["mypy-boto3-connect (>=1.33.0,<1.34.0)"] +connect-contact-lens = ["mypy-boto3-connect-contact-lens (>=1.33.0,<1.34.0)"] +connectcampaigns = ["mypy-boto3-connectcampaigns (>=1.33.0,<1.34.0)"] +connectcases = ["mypy-boto3-connectcases (>=1.33.0,<1.34.0)"] +connectparticipant = ["mypy-boto3-connectparticipant (>=1.33.0,<1.34.0)"] +controltower = ["mypy-boto3-controltower (>=1.33.0,<1.34.0)"] +cost-optimization-hub = ["mypy-boto3-cost-optimization-hub (>=1.33.0,<1.34.0)"] +cur = ["mypy-boto3-cur (>=1.33.0,<1.34.0)"] +customer-profiles = ["mypy-boto3-customer-profiles (>=1.33.0,<1.34.0)"] +databrew = ["mypy-boto3-databrew (>=1.33.0,<1.34.0)"] +dataexchange = ["mypy-boto3-dataexchange (>=1.33.0,<1.34.0)"] +datapipeline = ["mypy-boto3-datapipeline (>=1.33.0,<1.34.0)"] +datasync = ["mypy-boto3-datasync (>=1.33.0,<1.34.0)"] +datazone = ["mypy-boto3-datazone (>=1.33.0,<1.34.0)"] +dax = ["mypy-boto3-dax (>=1.33.0,<1.34.0)"] +detective = ["mypy-boto3-detective (>=1.33.0,<1.34.0)"] +devicefarm = ["mypy-boto3-devicefarm (>=1.33.0,<1.34.0)"] +devops-guru = ["mypy-boto3-devops-guru (>=1.33.0,<1.34.0)"] +directconnect = ["mypy-boto3-directconnect (>=1.33.0,<1.34.0)"] +discovery = ["mypy-boto3-discovery (>=1.33.0,<1.34.0)"] +dlm = ["mypy-boto3-dlm (>=1.33.0,<1.34.0)"] +dms = ["mypy-boto3-dms (>=1.33.0,<1.34.0)"] +docdb = ["mypy-boto3-docdb (>=1.33.0,<1.34.0)"] +docdb-elastic = ["mypy-boto3-docdb-elastic (>=1.33.0,<1.34.0)"] +drs = ["mypy-boto3-drs (>=1.33.0,<1.34.0)"] +ds = ["mypy-boto3-ds (>=1.33.0,<1.34.0)"] +dynamodb = ["mypy-boto3-dynamodb (>=1.33.0,<1.34.0)"] +dynamodbstreams = ["mypy-boto3-dynamodbstreams (>=1.33.0,<1.34.0)"] +ebs = ["mypy-boto3-ebs (>=1.33.0,<1.34.0)"] +ec2 = ["mypy-boto3-ec2 (>=1.33.0,<1.34.0)"] +ec2-instance-connect = ["mypy-boto3-ec2-instance-connect (>=1.33.0,<1.34.0)"] +ecr = ["mypy-boto3-ecr (>=1.33.0,<1.34.0)"] +ecr-public = ["mypy-boto3-ecr-public (>=1.33.0,<1.34.0)"] +ecs = ["mypy-boto3-ecs (>=1.33.0,<1.34.0)"] +efs = ["mypy-boto3-efs (>=1.33.0,<1.34.0)"] +eks = ["mypy-boto3-eks (>=1.33.0,<1.34.0)"] +eks-auth = ["mypy-boto3-eks-auth (>=1.33.0,<1.34.0)"] +elastic-inference = ["mypy-boto3-elastic-inference (>=1.33.0,<1.34.0)"] +elasticache = ["mypy-boto3-elasticache (>=1.33.0,<1.34.0)"] +elasticbeanstalk = ["mypy-boto3-elasticbeanstalk (>=1.33.0,<1.34.0)"] +elastictranscoder = ["mypy-boto3-elastictranscoder (>=1.33.0,<1.34.0)"] +elb = ["mypy-boto3-elb (>=1.33.0,<1.34.0)"] +elbv2 = ["mypy-boto3-elbv2 (>=1.33.0,<1.34.0)"] +emr = ["mypy-boto3-emr (>=1.33.0,<1.34.0)"] +emr-containers = ["mypy-boto3-emr-containers (>=1.33.0,<1.34.0)"] +emr-serverless = ["mypy-boto3-emr-serverless (>=1.33.0,<1.34.0)"] +entityresolution = ["mypy-boto3-entityresolution (>=1.33.0,<1.34.0)"] +es = ["mypy-boto3-es (>=1.33.0,<1.34.0)"] +essential = ["mypy-boto3-cloudformation (>=1.33.0,<1.34.0)", "mypy-boto3-dynamodb (>=1.33.0,<1.34.0)", "mypy-boto3-ec2 (>=1.33.0,<1.34.0)", "mypy-boto3-lambda (>=1.33.0,<1.34.0)", "mypy-boto3-rds (>=1.33.0,<1.34.0)", "mypy-boto3-s3 (>=1.33.0,<1.34.0)", "mypy-boto3-sqs (>=1.33.0,<1.34.0)"] +events = ["mypy-boto3-events (>=1.33.0,<1.34.0)"] +evidently = ["mypy-boto3-evidently (>=1.33.0,<1.34.0)"] +finspace = ["mypy-boto3-finspace (>=1.33.0,<1.34.0)"] +finspace-data = ["mypy-boto3-finspace-data (>=1.33.0,<1.34.0)"] +firehose = ["mypy-boto3-firehose (>=1.33.0,<1.34.0)"] +fis = ["mypy-boto3-fis (>=1.33.0,<1.34.0)"] +fms = ["mypy-boto3-fms (>=1.33.0,<1.34.0)"] +forecast = ["mypy-boto3-forecast (>=1.33.0,<1.34.0)"] +forecastquery = ["mypy-boto3-forecastquery (>=1.33.0,<1.34.0)"] +frauddetector = ["mypy-boto3-frauddetector (>=1.33.0,<1.34.0)"] +freetier = ["mypy-boto3-freetier (>=1.33.0,<1.34.0)"] +fsx = ["mypy-boto3-fsx (>=1.33.0,<1.34.0)"] +gamelift = ["mypy-boto3-gamelift (>=1.33.0,<1.34.0)"] +glacier = ["mypy-boto3-glacier (>=1.33.0,<1.34.0)"] +globalaccelerator = ["mypy-boto3-globalaccelerator (>=1.33.0,<1.34.0)"] +glue = ["mypy-boto3-glue (>=1.33.0,<1.34.0)"] +grafana = ["mypy-boto3-grafana (>=1.33.0,<1.34.0)"] +greengrass = ["mypy-boto3-greengrass (>=1.33.0,<1.34.0)"] +greengrassv2 = ["mypy-boto3-greengrassv2 (>=1.33.0,<1.34.0)"] +groundstation = ["mypy-boto3-groundstation (>=1.33.0,<1.34.0)"] +guardduty = ["mypy-boto3-guardduty (>=1.33.0,<1.34.0)"] +health = ["mypy-boto3-health (>=1.33.0,<1.34.0)"] +healthlake = ["mypy-boto3-healthlake (>=1.33.0,<1.34.0)"] +honeycode = ["mypy-boto3-honeycode (>=1.33.0,<1.34.0)"] +iam = ["mypy-boto3-iam (>=1.33.0,<1.34.0)"] +identitystore = ["mypy-boto3-identitystore (>=1.33.0,<1.34.0)"] +imagebuilder = ["mypy-boto3-imagebuilder (>=1.33.0,<1.34.0)"] +importexport = ["mypy-boto3-importexport (>=1.33.0,<1.34.0)"] +inspector = ["mypy-boto3-inspector (>=1.33.0,<1.34.0)"] +inspector-scan = ["mypy-boto3-inspector-scan (>=1.33.0,<1.34.0)"] +inspector2 = ["mypy-boto3-inspector2 (>=1.33.0,<1.34.0)"] +internetmonitor = ["mypy-boto3-internetmonitor (>=1.33.0,<1.34.0)"] +iot = ["mypy-boto3-iot (>=1.33.0,<1.34.0)"] +iot-data = ["mypy-boto3-iot-data (>=1.33.0,<1.34.0)"] +iot-jobs-data = ["mypy-boto3-iot-jobs-data (>=1.33.0,<1.34.0)"] +iot-roborunner = ["mypy-boto3-iot-roborunner (>=1.33.0,<1.34.0)"] +iot1click-devices = ["mypy-boto3-iot1click-devices (>=1.33.0,<1.34.0)"] +iot1click-projects = ["mypy-boto3-iot1click-projects (>=1.33.0,<1.34.0)"] +iotanalytics = ["mypy-boto3-iotanalytics (>=1.33.0,<1.34.0)"] +iotdeviceadvisor = ["mypy-boto3-iotdeviceadvisor (>=1.33.0,<1.34.0)"] +iotevents = ["mypy-boto3-iotevents (>=1.33.0,<1.34.0)"] +iotevents-data = ["mypy-boto3-iotevents-data (>=1.33.0,<1.34.0)"] +iotfleethub = ["mypy-boto3-iotfleethub (>=1.33.0,<1.34.0)"] +iotfleetwise = ["mypy-boto3-iotfleetwise (>=1.33.0,<1.34.0)"] +iotsecuretunneling = ["mypy-boto3-iotsecuretunneling (>=1.33.0,<1.34.0)"] +iotsitewise = ["mypy-boto3-iotsitewise (>=1.33.0,<1.34.0)"] +iotthingsgraph = ["mypy-boto3-iotthingsgraph (>=1.33.0,<1.34.0)"] +iottwinmaker = ["mypy-boto3-iottwinmaker (>=1.33.0,<1.34.0)"] +iotwireless = ["mypy-boto3-iotwireless (>=1.33.0,<1.34.0)"] +ivs = ["mypy-boto3-ivs (>=1.33.0,<1.34.0)"] +ivs-realtime = ["mypy-boto3-ivs-realtime (>=1.33.0,<1.34.0)"] +ivschat = ["mypy-boto3-ivschat (>=1.33.0,<1.34.0)"] +kafka = ["mypy-boto3-kafka (>=1.33.0,<1.34.0)"] +kafkaconnect = ["mypy-boto3-kafkaconnect (>=1.33.0,<1.34.0)"] +kendra = ["mypy-boto3-kendra (>=1.33.0,<1.34.0)"] +kendra-ranking = ["mypy-boto3-kendra-ranking (>=1.33.0,<1.34.0)"] +keyspaces = ["mypy-boto3-keyspaces (>=1.33.0,<1.34.0)"] +kinesis = ["mypy-boto3-kinesis (>=1.33.0,<1.34.0)"] +kinesis-video-archived-media = ["mypy-boto3-kinesis-video-archived-media (>=1.33.0,<1.34.0)"] +kinesis-video-media = ["mypy-boto3-kinesis-video-media (>=1.33.0,<1.34.0)"] +kinesis-video-signaling = ["mypy-boto3-kinesis-video-signaling (>=1.33.0,<1.34.0)"] +kinesis-video-webrtc-storage = ["mypy-boto3-kinesis-video-webrtc-storage (>=1.33.0,<1.34.0)"] +kinesisanalytics = ["mypy-boto3-kinesisanalytics (>=1.33.0,<1.34.0)"] +kinesisanalyticsv2 = ["mypy-boto3-kinesisanalyticsv2 (>=1.33.0,<1.34.0)"] +kinesisvideo = ["mypy-boto3-kinesisvideo (>=1.33.0,<1.34.0)"] +kms = ["mypy-boto3-kms (>=1.33.0,<1.34.0)"] +lakeformation = ["mypy-boto3-lakeformation (>=1.33.0,<1.34.0)"] +lambda = ["mypy-boto3-lambda (>=1.33.0,<1.34.0)"] +launch-wizard = ["mypy-boto3-launch-wizard (>=1.33.0,<1.34.0)"] +lex-models = ["mypy-boto3-lex-models (>=1.33.0,<1.34.0)"] +lex-runtime = ["mypy-boto3-lex-runtime (>=1.33.0,<1.34.0)"] +lexv2-models = ["mypy-boto3-lexv2-models (>=1.33.0,<1.34.0)"] +lexv2-runtime = ["mypy-boto3-lexv2-runtime (>=1.33.0,<1.34.0)"] +license-manager = ["mypy-boto3-license-manager (>=1.33.0,<1.34.0)"] +license-manager-linux-subscriptions = ["mypy-boto3-license-manager-linux-subscriptions (>=1.33.0,<1.34.0)"] +license-manager-user-subscriptions = ["mypy-boto3-license-manager-user-subscriptions (>=1.33.0,<1.34.0)"] +lightsail = ["mypy-boto3-lightsail (>=1.33.0,<1.34.0)"] +location = ["mypy-boto3-location (>=1.33.0,<1.34.0)"] +logs = ["mypy-boto3-logs (>=1.33.0,<1.34.0)"] +lookoutequipment = ["mypy-boto3-lookoutequipment (>=1.33.0,<1.34.0)"] +lookoutmetrics = ["mypy-boto3-lookoutmetrics (>=1.33.0,<1.34.0)"] +lookoutvision = ["mypy-boto3-lookoutvision (>=1.33.0,<1.34.0)"] +m2 = ["mypy-boto3-m2 (>=1.33.0,<1.34.0)"] +machinelearning = ["mypy-boto3-machinelearning (>=1.33.0,<1.34.0)"] +macie2 = ["mypy-boto3-macie2 (>=1.33.0,<1.34.0)"] +managedblockchain = ["mypy-boto3-managedblockchain (>=1.33.0,<1.34.0)"] +managedblockchain-query = ["mypy-boto3-managedblockchain-query (>=1.33.0,<1.34.0)"] +marketplace-agreement = ["mypy-boto3-marketplace-agreement (>=1.33.0,<1.34.0)"] +marketplace-catalog = ["mypy-boto3-marketplace-catalog (>=1.33.0,<1.34.0)"] +marketplace-deployment = ["mypy-boto3-marketplace-deployment (>=1.33.0,<1.34.0)"] +marketplace-entitlement = ["mypy-boto3-marketplace-entitlement (>=1.33.0,<1.34.0)"] +marketplacecommerceanalytics = ["mypy-boto3-marketplacecommerceanalytics (>=1.33.0,<1.34.0)"] +mediaconnect = ["mypy-boto3-mediaconnect (>=1.33.0,<1.34.0)"] +mediaconvert = ["mypy-boto3-mediaconvert (>=1.33.0,<1.34.0)"] +medialive = ["mypy-boto3-medialive (>=1.33.0,<1.34.0)"] +mediapackage = ["mypy-boto3-mediapackage (>=1.33.0,<1.34.0)"] +mediapackage-vod = ["mypy-boto3-mediapackage-vod (>=1.33.0,<1.34.0)"] +mediapackagev2 = ["mypy-boto3-mediapackagev2 (>=1.33.0,<1.34.0)"] +mediastore = ["mypy-boto3-mediastore (>=1.33.0,<1.34.0)"] +mediastore-data = ["mypy-boto3-mediastore-data (>=1.33.0,<1.34.0)"] +mediatailor = ["mypy-boto3-mediatailor (>=1.33.0,<1.34.0)"] +medical-imaging = ["mypy-boto3-medical-imaging (>=1.33.0,<1.34.0)"] +memorydb = ["mypy-boto3-memorydb (>=1.33.0,<1.34.0)"] +meteringmarketplace = ["mypy-boto3-meteringmarketplace (>=1.33.0,<1.34.0)"] +mgh = ["mypy-boto3-mgh (>=1.33.0,<1.34.0)"] +mgn = ["mypy-boto3-mgn (>=1.33.0,<1.34.0)"] +migration-hub-refactor-spaces = ["mypy-boto3-migration-hub-refactor-spaces (>=1.33.0,<1.34.0)"] +migrationhub-config = ["mypy-boto3-migrationhub-config (>=1.33.0,<1.34.0)"] +migrationhuborchestrator = ["mypy-boto3-migrationhuborchestrator (>=1.33.0,<1.34.0)"] +migrationhubstrategy = ["mypy-boto3-migrationhubstrategy (>=1.33.0,<1.34.0)"] +mobile = ["mypy-boto3-mobile (>=1.33.0,<1.34.0)"] +mq = ["mypy-boto3-mq (>=1.33.0,<1.34.0)"] +mturk = ["mypy-boto3-mturk (>=1.33.0,<1.34.0)"] +mwaa = ["mypy-boto3-mwaa (>=1.33.0,<1.34.0)"] +neptune = ["mypy-boto3-neptune (>=1.33.0,<1.34.0)"] +neptunedata = ["mypy-boto3-neptunedata (>=1.33.0,<1.34.0)"] +network-firewall = ["mypy-boto3-network-firewall (>=1.33.0,<1.34.0)"] +networkmanager = ["mypy-boto3-networkmanager (>=1.33.0,<1.34.0)"] +nimble = ["mypy-boto3-nimble (>=1.33.0,<1.34.0)"] +oam = ["mypy-boto3-oam (>=1.33.0,<1.34.0)"] +omics = ["mypy-boto3-omics (>=1.33.0,<1.34.0)"] +opensearch = ["mypy-boto3-opensearch (>=1.33.0,<1.34.0)"] +opensearchserverless = ["mypy-boto3-opensearchserverless (>=1.33.0,<1.34.0)"] +opsworks = ["mypy-boto3-opsworks (>=1.33.0,<1.34.0)"] +opsworkscm = ["mypy-boto3-opsworkscm (>=1.33.0,<1.34.0)"] +organizations = ["mypy-boto3-organizations (>=1.33.0,<1.34.0)"] +osis = ["mypy-boto3-osis (>=1.33.0,<1.34.0)"] +outposts = ["mypy-boto3-outposts (>=1.33.0,<1.34.0)"] +panorama = ["mypy-boto3-panorama (>=1.33.0,<1.34.0)"] +payment-cryptography = ["mypy-boto3-payment-cryptography (>=1.33.0,<1.34.0)"] +payment-cryptography-data = ["mypy-boto3-payment-cryptography-data (>=1.33.0,<1.34.0)"] +pca-connector-ad = ["mypy-boto3-pca-connector-ad (>=1.33.0,<1.34.0)"] +personalize = ["mypy-boto3-personalize (>=1.33.0,<1.34.0)"] +personalize-events = ["mypy-boto3-personalize-events (>=1.33.0,<1.34.0)"] +personalize-runtime = ["mypy-boto3-personalize-runtime (>=1.33.0,<1.34.0)"] +pi = ["mypy-boto3-pi (>=1.33.0,<1.34.0)"] +pinpoint = ["mypy-boto3-pinpoint (>=1.33.0,<1.34.0)"] +pinpoint-email = ["mypy-boto3-pinpoint-email (>=1.33.0,<1.34.0)"] +pinpoint-sms-voice = ["mypy-boto3-pinpoint-sms-voice (>=1.33.0,<1.34.0)"] +pinpoint-sms-voice-v2 = ["mypy-boto3-pinpoint-sms-voice-v2 (>=1.33.0,<1.34.0)"] +pipes = ["mypy-boto3-pipes (>=1.33.0,<1.34.0)"] +polly = ["mypy-boto3-polly (>=1.33.0,<1.34.0)"] +pricing = ["mypy-boto3-pricing (>=1.33.0,<1.34.0)"] +privatenetworks = ["mypy-boto3-privatenetworks (>=1.33.0,<1.34.0)"] +proton = ["mypy-boto3-proton (>=1.33.0,<1.34.0)"] +qbusiness = ["mypy-boto3-qbusiness (>=1.33.0,<1.34.0)"] +qconnect = ["mypy-boto3-qconnect (>=1.33.0,<1.34.0)"] +qldb = ["mypy-boto3-qldb (>=1.33.0,<1.34.0)"] +qldb-session = ["mypy-boto3-qldb-session (>=1.33.0,<1.34.0)"] +quicksight = ["mypy-boto3-quicksight (>=1.33.0,<1.34.0)"] +ram = ["mypy-boto3-ram (>=1.33.0,<1.34.0)"] +rbin = ["mypy-boto3-rbin (>=1.33.0,<1.34.0)"] +rds = ["mypy-boto3-rds (>=1.33.0,<1.34.0)"] +rds-data = ["mypy-boto3-rds-data (>=1.33.0,<1.34.0)"] +redshift = ["mypy-boto3-redshift (>=1.33.0,<1.34.0)"] +redshift-data = ["mypy-boto3-redshift-data (>=1.33.0,<1.34.0)"] +redshift-serverless = ["mypy-boto3-redshift-serverless (>=1.33.0,<1.34.0)"] +rekognition = ["mypy-boto3-rekognition (>=1.33.0,<1.34.0)"] +repostspace = ["mypy-boto3-repostspace (>=1.33.0,<1.34.0)"] +resiliencehub = ["mypy-boto3-resiliencehub (>=1.33.0,<1.34.0)"] +resource-explorer-2 = ["mypy-boto3-resource-explorer-2 (>=1.33.0,<1.34.0)"] +resource-groups = ["mypy-boto3-resource-groups (>=1.33.0,<1.34.0)"] +resourcegroupstaggingapi = ["mypy-boto3-resourcegroupstaggingapi (>=1.33.0,<1.34.0)"] +robomaker = ["mypy-boto3-robomaker (>=1.33.0,<1.34.0)"] +rolesanywhere = ["mypy-boto3-rolesanywhere (>=1.33.0,<1.34.0)"] +route53 = ["mypy-boto3-route53 (>=1.33.0,<1.34.0)"] +route53-recovery-cluster = ["mypy-boto3-route53-recovery-cluster (>=1.33.0,<1.34.0)"] +route53-recovery-control-config = ["mypy-boto3-route53-recovery-control-config (>=1.33.0,<1.34.0)"] +route53-recovery-readiness = ["mypy-boto3-route53-recovery-readiness (>=1.33.0,<1.34.0)"] +route53domains = ["mypy-boto3-route53domains (>=1.33.0,<1.34.0)"] +route53resolver = ["mypy-boto3-route53resolver (>=1.33.0,<1.34.0)"] +rum = ["mypy-boto3-rum (>=1.33.0,<1.34.0)"] +s3 = ["mypy-boto3-s3 (>=1.33.0,<1.34.0)"] +s3control = ["mypy-boto3-s3control (>=1.33.0,<1.34.0)"] +s3outposts = ["mypy-boto3-s3outposts (>=1.33.0,<1.34.0)"] +sagemaker = ["mypy-boto3-sagemaker (>=1.33.0,<1.34.0)"] +sagemaker-a2i-runtime = ["mypy-boto3-sagemaker-a2i-runtime (>=1.33.0,<1.34.0)"] +sagemaker-edge = ["mypy-boto3-sagemaker-edge (>=1.33.0,<1.34.0)"] +sagemaker-featurestore-runtime = ["mypy-boto3-sagemaker-featurestore-runtime (>=1.33.0,<1.34.0)"] +sagemaker-geospatial = ["mypy-boto3-sagemaker-geospatial (>=1.33.0,<1.34.0)"] +sagemaker-metrics = ["mypy-boto3-sagemaker-metrics (>=1.33.0,<1.34.0)"] +sagemaker-runtime = ["mypy-boto3-sagemaker-runtime (>=1.33.0,<1.34.0)"] +savingsplans = ["mypy-boto3-savingsplans (>=1.33.0,<1.34.0)"] +scheduler = ["mypy-boto3-scheduler (>=1.33.0,<1.34.0)"] +schemas = ["mypy-boto3-schemas (>=1.33.0,<1.34.0)"] +sdb = ["mypy-boto3-sdb (>=1.33.0,<1.34.0)"] +secretsmanager = ["mypy-boto3-secretsmanager (>=1.33.0,<1.34.0)"] +securityhub = ["mypy-boto3-securityhub (>=1.33.0,<1.34.0)"] +securitylake = ["mypy-boto3-securitylake (>=1.33.0,<1.34.0)"] +serverlessrepo = ["mypy-boto3-serverlessrepo (>=1.33.0,<1.34.0)"] +service-quotas = ["mypy-boto3-service-quotas (>=1.33.0,<1.34.0)"] +servicecatalog = ["mypy-boto3-servicecatalog (>=1.33.0,<1.34.0)"] +servicecatalog-appregistry = ["mypy-boto3-servicecatalog-appregistry (>=1.33.0,<1.34.0)"] +servicediscovery = ["mypy-boto3-servicediscovery (>=1.33.0,<1.34.0)"] +ses = ["mypy-boto3-ses (>=1.33.0,<1.34.0)"] +sesv2 = ["mypy-boto3-sesv2 (>=1.33.0,<1.34.0)"] +shield = ["mypy-boto3-shield (>=1.33.0,<1.34.0)"] +signer = ["mypy-boto3-signer (>=1.33.0,<1.34.0)"] +simspaceweaver = ["mypy-boto3-simspaceweaver (>=1.33.0,<1.34.0)"] +sms = ["mypy-boto3-sms (>=1.33.0,<1.34.0)"] +sms-voice = ["mypy-boto3-sms-voice (>=1.33.0,<1.34.0)"] +snow-device-management = ["mypy-boto3-snow-device-management (>=1.33.0,<1.34.0)"] +snowball = ["mypy-boto3-snowball (>=1.33.0,<1.34.0)"] +sns = ["mypy-boto3-sns (>=1.33.0,<1.34.0)"] +sqs = ["mypy-boto3-sqs (>=1.33.0,<1.34.0)"] +ssm = ["mypy-boto3-ssm (>=1.33.0,<1.34.0)"] +ssm-contacts = ["mypy-boto3-ssm-contacts (>=1.33.0,<1.34.0)"] +ssm-incidents = ["mypy-boto3-ssm-incidents (>=1.33.0,<1.34.0)"] +ssm-sap = ["mypy-boto3-ssm-sap (>=1.33.0,<1.34.0)"] +sso = ["mypy-boto3-sso (>=1.33.0,<1.34.0)"] +sso-admin = ["mypy-boto3-sso-admin (>=1.33.0,<1.34.0)"] +sso-oidc = ["mypy-boto3-sso-oidc (>=1.33.0,<1.34.0)"] +stepfunctions = ["mypy-boto3-stepfunctions (>=1.33.0,<1.34.0)"] +storagegateway = ["mypy-boto3-storagegateway (>=1.33.0,<1.34.0)"] +sts = ["mypy-boto3-sts (>=1.33.0,<1.34.0)"] +support = ["mypy-boto3-support (>=1.33.0,<1.34.0)"] +support-app = ["mypy-boto3-support-app (>=1.33.0,<1.34.0)"] +swf = ["mypy-boto3-swf (>=1.33.0,<1.34.0)"] +synthetics = ["mypy-boto3-synthetics (>=1.33.0,<1.34.0)"] +textract = ["mypy-boto3-textract (>=1.33.0,<1.34.0)"] +timestream-query = ["mypy-boto3-timestream-query (>=1.33.0,<1.34.0)"] +timestream-write = ["mypy-boto3-timestream-write (>=1.33.0,<1.34.0)"] +tnb = ["mypy-boto3-tnb (>=1.33.0,<1.34.0)"] +transcribe = ["mypy-boto3-transcribe (>=1.33.0,<1.34.0)"] +transfer = ["mypy-boto3-transfer (>=1.33.0,<1.34.0)"] +translate = ["mypy-boto3-translate (>=1.33.0,<1.34.0)"] +trustedadvisor = ["mypy-boto3-trustedadvisor (>=1.33.0,<1.34.0)"] +verifiedpermissions = ["mypy-boto3-verifiedpermissions (>=1.33.0,<1.34.0)"] +voice-id = ["mypy-boto3-voice-id (>=1.33.0,<1.34.0)"] +vpc-lattice = ["mypy-boto3-vpc-lattice (>=1.33.0,<1.34.0)"] +waf = ["mypy-boto3-waf (>=1.33.0,<1.34.0)"] +waf-regional = ["mypy-boto3-waf-regional (>=1.33.0,<1.34.0)"] +wafv2 = ["mypy-boto3-wafv2 (>=1.33.0,<1.34.0)"] +wellarchitected = ["mypy-boto3-wellarchitected (>=1.33.0,<1.34.0)"] +wisdom = ["mypy-boto3-wisdom (>=1.33.0,<1.34.0)"] +workdocs = ["mypy-boto3-workdocs (>=1.33.0,<1.34.0)"] +worklink = ["mypy-boto3-worklink (>=1.33.0,<1.34.0)"] +workmail = ["mypy-boto3-workmail (>=1.33.0,<1.34.0)"] +workmailmessageflow = ["mypy-boto3-workmailmessageflow (>=1.33.0,<1.34.0)"] +workspaces = ["mypy-boto3-workspaces (>=1.33.0,<1.34.0)"] +workspaces-thin-client = ["mypy-boto3-workspaces-thin-client (>=1.33.0,<1.34.0)"] +workspaces-web = ["mypy-boto3-workspaces-web (>=1.33.0,<1.34.0)"] +xray = ["mypy-boto3-xray (>=1.33.0,<1.34.0)"] + +[[package]] +name = "botocore" +version = "1.33.8" +description = "Low-level, data-driven core of boto 3." +optional = false +python-versions = ">= 3.7" +files = [ + {file = "botocore-1.33.8-py3-none-any.whl", hash = "sha256:90236e6e69d7e80875d7f9d39383630706edbc1298026698c6c70d9b6a65576e"}, + {file = "botocore-1.33.8.tar.gz", hash = "sha256:e6970bf89cbe2624399aeffce52c253917d8e5a1c671de4054557603ab56c922"}, +] + +[package.dependencies] +jmespath = ">=0.7.1,<2.0.0" +python-dateutil = ">=2.1,<3.0.0" +urllib3 = {version = ">=1.25.4,<2.1", markers = "python_version >= \"3.10\""} + +[package.extras] +crt = ["awscrt (==0.19.17)"] + +[[package]] +name = "botocore-stubs" +version = "1.33.8" +description = "Type annotations and code completion for botocore" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "botocore_stubs-1.33.8-py3-none-any.whl", hash = "sha256:774ed54c8a9c599f0614900aafe79b78689dc2460e6752b18c50c7d645de5799"}, + {file = "botocore_stubs-1.33.8.tar.gz", hash = "sha256:0356ff0fc918d8def8bdfc78523791eb4ce55c255c1ebe49ba105e168c817dcd"}, +] + +[package.dependencies] +types-awscrt = "*" + +[package.extras] +botocore = ["botocore"] + [[package]] name = "brotli" version = "1.1.0" @@ -1658,13 +2109,13 @@ uritemplate = ">=3.0.1,<5" [[package]] name = "google-auth" -version = "2.24.0" +version = "2.25.0" description = "Google Authentication Library" optional = false python-versions = ">=3.7" files = [ - {file = "google-auth-2.24.0.tar.gz", hash = "sha256:2ec7b2a506989d7dbfdbe81cb8d0ead8876caaed14f86d29d34483cbe99c57af"}, - {file = "google_auth-2.24.0-py2.py3-none-any.whl", hash = "sha256:9b82d5c8d3479a5391ea0a46d81cca698d328459da31d4a459d4e901a5d927e0"}, + {file = "google-auth-2.25.0.tar.gz", hash = "sha256:cabc5c6fd327d71baa820f745118ecb2b709412d153419a43173d91495ef8715"}, + {file = "google_auth-2.25.0-py2.py3-none-any.whl", hash = "sha256:2e7d37702e617ea9f4ac2568625a9fc2cdea910dcdca90e378db09ac94ee9ab3"}, ] [package.dependencies] @@ -1769,6 +2220,126 @@ proto-plus = [ ] protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" +[[package]] +name = "google-cloud-storage" +version = "2.13.0" +description = "Google Cloud Storage API client library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-cloud-storage-2.13.0.tar.gz", hash = "sha256:f62dc4c7b6cd4360d072e3deb28035fbdad491ac3d9b0b1815a12daea10f37c7"}, + {file = "google_cloud_storage-2.13.0-py2.py3-none-any.whl", hash = "sha256:ab0bf2e1780a1b74cf17fccb13788070b729f50c252f0c94ada2aae0ca95437d"}, +] + +[package.dependencies] +google-api-core = ">=1.31.5,<2.0.dev0 || >2.3.0,<3.0.0dev" +google-auth = ">=2.23.3,<3.0dev" +google-cloud-core = ">=2.3.0,<3.0dev" +google-crc32c = ">=1.0,<2.0dev" +google-resumable-media = ">=2.6.0" +requests = ">=2.18.0,<3.0.0dev" + +[package.extras] +protobuf = ["protobuf (<5.0.0dev)"] + +[[package]] +name = "google-crc32c" +version = "1.5.0" +description = "A python wrapper of the C library 'Google CRC32C'" +optional = false +python-versions = ">=3.7" +files = [ + {file = "google-crc32c-1.5.0.tar.gz", hash = "sha256:89284716bc6a5a415d4eaa11b1726d2d60a0cd12aadf5439828353662ede9dd7"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:596d1f98fc70232fcb6590c439f43b350cb762fb5d61ce7b0e9db4539654cc13"}, + {file = "google_crc32c-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:be82c3c8cfb15b30f36768797a640e800513793d6ae1724aaaafe5bf86f8f346"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:461665ff58895f508e2866824a47bdee72497b091c730071f2b7575d5762ab65"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2096eddb4e7c7bdae4bd69ad364e55e07b8316653234a56552d9c988bd2d61b"}, + {file = "google_crc32c-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:116a7c3c616dd14a3de8c64a965828b197e5f2d121fedd2f8c5585c547e87b02"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5829b792bf5822fd0a6f6eb34c5f81dd074f01d570ed7f36aa101d6fc7a0a6e4"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:64e52e2b3970bd891309c113b54cf0e4384762c934d5ae56e283f9a0afcd953e"}, + {file = "google_crc32c-1.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:02ebb8bf46c13e36998aeaad1de9b48f4caf545e91d14041270d9dca767b780c"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win32.whl", hash = "sha256:2e920d506ec85eb4ba50cd4228c2bec05642894d4c73c59b3a2fe20346bd00ee"}, + {file = "google_crc32c-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:07eb3c611ce363c51a933bf6bd7f8e3878a51d124acfc89452a75120bc436289"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cae0274952c079886567f3f4f685bcaf5708f0a23a5f5216fdab71f81a6c0273"}, + {file = "google_crc32c-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1034d91442ead5a95b5aaef90dbfaca8633b0247d1e41621d1e9f9db88c36298"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c42c70cd1d362284289c6273adda4c6af8039a8ae12dc451dcd61cdabb8ab57"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8485b340a6a9e76c62a7dce3c98e5f102c9219f4cfbf896a00cf48caf078d438"}, + {file = "google_crc32c-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77e2fd3057c9d78e225fa0a2160f96b64a824de17840351b26825b0848022906"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:f583edb943cf2e09c60441b910d6a20b4d9d626c75a36c8fcac01a6c96c01183"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a1fd716e7a01f8e717490fbe2e431d2905ab8aa598b9b12f8d10abebb36b04dd"}, + {file = "google_crc32c-1.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:72218785ce41b9cfd2fc1d6a017dc1ff7acfc4c17d01053265c41a2c0cc39b8c"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win32.whl", hash = "sha256:66741ef4ee08ea0b2cc3c86916ab66b6aef03768525627fd6a1b34968b4e3709"}, + {file = "google_crc32c-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ba1eb1843304b1e5537e1fca632fa894d6f6deca8d6389636ee5b4797affb968"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:98cb4d057f285bd80d8778ebc4fde6b4d509ac3f331758fb1528b733215443ae"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fd8536e902db7e365f49e7d9029283403974ccf29b13fc7028b97e2295b33556"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19e0a019d2c4dcc5e598cd4a4bc7b008546b0358bd322537c74ad47a5386884f"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02c65b9817512edc6a4ae7c7e987fea799d2e0ee40c53ec573a692bee24de876"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6ac08d24c1f16bd2bf5eca8eaf8304812f44af5cfe5062006ec676e7e1d50afc"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:3359fc442a743e870f4588fcf5dcbc1bf929df1fad8fb9905cd94e5edb02e84c"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1e986b206dae4476f41bcec1faa057851f3889503a70e1bdb2378d406223994a"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:de06adc872bcd8c2a4e0dc51250e9e65ef2ca91be023b9d13ebd67c2ba552e1e"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:d3515f198eaa2f0ed49f8819d5732d70698c3fa37384146079b3799b97667a94"}, + {file = "google_crc32c-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:67b741654b851abafb7bc625b6d1cdd520a379074e64b6a128e3b688c3c04740"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c02ec1c5856179f171e032a31d6f8bf84e5a75c45c33b2e20a3de353b266ebd8"}, + {file = "google_crc32c-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edfedb64740750e1a3b16152620220f51d58ff1b4abceb339ca92e934775c27a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:84e6e8cd997930fc66d5bb4fde61e2b62ba19d62b7abd7a69920406f9ecca946"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:024894d9d3cfbc5943f8f230e23950cd4906b2fe004c72e29b209420a1e6b05a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:998679bf62b7fb599d2878aa3ed06b9ce688b8974893e7223c60db155f26bd8d"}, + {file = "google_crc32c-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:83c681c526a3439b5cf94f7420471705bbf96262f49a6fe546a6db5f687a3d4a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4c6fdd4fccbec90cc8a01fc00773fcd5fa28db683c116ee3cb35cd5da9ef6c37"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5ae44e10a8e3407dbe138984f21e536583f2bba1be9491239f942c2464ac0894"}, + {file = "google_crc32c-1.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:37933ec6e693e51a5b07505bd05de57eee12f3e8c32b07da7e73669398e6630a"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win32.whl", hash = "sha256:fe70e325aa68fa4b5edf7d1a4b6f691eb04bbccac0ace68e34820d283b5f80d4"}, + {file = "google_crc32c-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:74dea7751d98034887dbd821b7aae3e1d36eda111d6ca36c206c44478035709c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c6c777a480337ac14f38564ac88ae82d4cd238bf293f0a22295b66eb89ffced7"}, + {file = "google_crc32c-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:759ce4851a4bb15ecabae28f4d2e18983c244eddd767f560165563bf9aefbc8d"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f13cae8cc389a440def0c8c52057f37359014ccbc9dc1f0827936bcd367c6100"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e560628513ed34759456a416bf86b54b2476c59144a9138165c9a1575801d0d9"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1674e4307fa3024fc897ca774e9c7562c957af85df55efe2988ed9056dc4e57"}, + {file = "google_crc32c-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:278d2ed7c16cfc075c91378c4f47924c0625f5fc84b2d50d921b18b7975bd210"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d5280312b9af0976231f9e317c20e4a61cd2f9629b7bfea6a693d1878a264ebd"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8b87e1a59c38f275c0e3676fc2ab6d59eccecfd460be267ac360cc31f7bcde96"}, + {file = "google_crc32c-1.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7c074fece789b5034b9b1404a1f8208fc2d4c6ce9decdd16e8220c5a793e6f61"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win32.whl", hash = "sha256:7f57f14606cd1dd0f0de396e1e53824c371e9544a822648cd76c034d209b559c"}, + {file = "google_crc32c-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:a2355cba1f4ad8b6988a4ca3feed5bff33f6af2d7f134852cf279c2aebfde541"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f314013e7dcd5cf45ab1945d92e713eec788166262ae8deb2cfacd53def27325"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b747a674c20a67343cb61d43fdd9207ce5da6a99f629c6e2541aa0e89215bcd"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f24ed114432de109aa9fd317278518a5af2d31ac2ea6b952b2f7782b43da091"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8667b48e7a7ef66afba2c81e1094ef526388d35b873966d8a9a447974ed9178"}, + {file = "google_crc32c-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:1c7abdac90433b09bad6c43a43af253e688c9cfc1c86d332aed13f9a7c7f65e2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6f998db4e71b645350b9ac28a2167e6632c239963ca9da411523bb439c5c514d"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c99616c853bb585301df6de07ca2cadad344fd1ada6d62bb30aec05219c45d2"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ad40e31093a4af319dadf503b2467ccdc8f67c72e4bcba97f8c10cb078207b5"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd67cf24a553339d5062eff51013780a00d6f97a39ca062781d06b3a73b15462"}, + {file = "google_crc32c-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:398af5e3ba9cf768787eef45c803ff9614cc3e22a5b2f7d7ae116df8b11e3314"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b1f8133c9a275df5613a451e73f36c2aea4fe13c5c8997e22cf355ebd7bd0728"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ba053c5f50430a3fcfd36f75aff9caeba0440b2d076afdb79a318d6ca245f88"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:272d3892a1e1a2dbc39cc5cde96834c236d5327e2122d3aaa19f6614531bb6eb"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:635f5d4dd18758a1fbd1049a8e8d2fee4ffed124462d837d1a02a0e009c3ab31"}, + {file = "google_crc32c-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c672d99a345849301784604bfeaeba4db0c7aae50b95be04dd651fd2a7310b93"}, +] + +[package.extras] +testing = ["pytest"] + +[[package]] +name = "google-resumable-media" +version = "2.6.0" +description = "Utilities for Google Media Downloads and Resumable Uploads" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "google-resumable-media-2.6.0.tar.gz", hash = "sha256:972852f6c65f933e15a4a210c2b96930763b47197cdf4aa5f5bea435efb626e7"}, + {file = "google_resumable_media-2.6.0-py2.py3-none-any.whl", hash = "sha256:fc03d344381970f79eebb632a3c18bb1828593a2dc5572b5f90115ef7d11e81b"}, +] + +[package.dependencies] +google-crc32c = ">=1.0,<2.0dev" + +[package.extras] +aiohttp = ["aiohttp (>=3.6.2,<4.0.0dev)", "google-auth (>=1.22.0,<2.0dev)"] +requests = ["requests (>=2.18.0,<3.0.0dev)"] + [[package]] name = "googleapis-common-protos" version = "1.61.0" @@ -2389,6 +2960,17 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +optional = false +python-versions = ">=3.7" +files = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] + [[package]] name = "jsonpickle" version = "3.0.2" @@ -3081,6 +3663,20 @@ install-types = ["pip"] mypyc = ["setuptools (>=50)"] reports = ["lxml"] +[[package]] +name = "mypy-boto3-s3" +version = "1.33.2" +description = "Type annotations for boto3.S3 1.33.2 service generated with mypy-boto3-builder 7.20.3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mypy-boto3-s3-1.33.2.tar.gz", hash = "sha256:f54a3ad3288f4e4719ebada3dde68c320507b0fc451d59bc68af7e6ab15cbdad"}, + {file = "mypy_boto3_s3-1.33.2-py3-none-any.whl", hash = "sha256:9d463df6def30de31a467d49ab92ff7795d46709d56eff6f52216a08bac27918"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.12\""} + [[package]] name = "mypy-extensions" version = "1.0.0" @@ -4688,6 +5284,23 @@ files = [ [package.dependencies] pyasn1 = ">=0.1.3" +[[package]] +name = "s3transfer" +version = "0.8.2" +description = "An Amazon S3 Transfer Manager" +optional = false +python-versions = ">= 3.7" +files = [ + {file = "s3transfer-0.8.2-py3-none-any.whl", hash = "sha256:c9e56cbe88b28d8e197cf841f1f0c130f246595e77ae5b5a05b69fe7cb83de76"}, + {file = "s3transfer-0.8.2.tar.gz", hash = "sha256:368ac6876a9e9ed91f6bc86581e319be08188dc60d50e0d56308ed5765446283"}, +] + +[package.dependencies] +botocore = ">=1.33.2,<2.0a.0" + +[package.extras] +crt = ["botocore[crt] (>=1.33.2,<2.0a.0)"] + [[package]] name = "selenium" version = "4.15.2" @@ -5480,6 +6093,17 @@ dev = ["autoflake (>=1.3.1,<2.0.0)", "flake8 (>=3.8.3,<4.0.0)", "pre-commit (>=2 doc = ["cairosvg (>=2.5.2,<3.0.0)", "mdx-include (>=1.4.1,<2.0.0)", "mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=8.1.4,<9.0.0)", "pillow (>=9.3.0,<10.0.0)"] test = ["black (>=22.3.0,<23.0.0)", "coverage (>=6.2,<7.0)", "isort (>=5.0.6,<6.0.0)", "mypy (==0.910)", "pytest (>=4.4.0,<8.0.0)", "pytest-cov (>=2.10.0,<5.0.0)", "pytest-sugar (>=0.9.4,<0.10.0)", "pytest-xdist (>=1.32.0,<4.0.0)", "rich (>=10.11.0,<14.0.0)", "shellingham (>=1.3.0,<2.0.0)"] +[[package]] +name = "types-awscrt" +version = "0.19.19" +description = "Type annotations and code completion for awscrt" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "types_awscrt-0.19.19-py3-none-any.whl", hash = "sha256:a577c4d60a7fb7e21b436a73207a66f6ba50329d578b347934c5d99d4d612901"}, + {file = "types_awscrt-0.19.19.tar.gz", hash = "sha256:850d5ad95d8f337b15fb154790f39af077faf5c08d43758fd750f379a87d5f73"}, +] + [[package]] name = "types-beautifulsoup4" version = "4.12.0.7" @@ -5552,6 +6176,17 @@ files = [ [package.dependencies] urllib3 = ">=2" +[[package]] +name = "types-s3transfer" +version = "0.8.2" +description = "Type annotations and code completion for s3transfer" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "types_s3transfer-0.8.2-py3-none-any.whl", hash = "sha256:5e084ebcf2704281c71b19d5da6e1544b50859367d034b50080d5316a76a9418"}, + {file = "types_s3transfer-0.8.2.tar.gz", hash = "sha256:2e41756fcf94775a9949afa856489ac4570308609b0493dfbd7b4d333eb423e6"}, +] + [[package]] name = "typing-extensions" version = "4.8.0" @@ -5587,13 +6222,13 @@ files = [ [[package]] name = "urllib3" -version = "2.1.0" +version = "2.0.7" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"}, + {file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"}, ] [package.dependencies] @@ -5601,6 +6236,7 @@ pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +secure = ["certifi", "cryptography (>=1.9)", "idna (>=2.0.0)", "pyopenssl (>=17.1.0)", "urllib3-secure-extra"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -6170,4 +6806,4 @@ benchmark = ["agbenchmark"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "5a078595fa997b1d1e8b9aaa0323198f4fcbc742f06017010cb5cce4565fa7ba" +content-hash = "8960f41ae9135a838fa64c37d965cbba42cc6f723e3429b67cd4c99923ec6ff8" diff --git a/autogpts/autogpt/pyproject.toml b/autogpts/autogpt/pyproject.toml index 40663e0841..2aec96a1dd 100644 --- a/autogpts/autogpt/pyproject.toml +++ b/autogpts/autogpt/pyproject.toml @@ -25,6 +25,7 @@ python = "^3.10" # autogpt-forge = { path = "../forge" } autogpt-forge = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", subdirectory = "autogpts/forge"} beautifulsoup4 = "^4.12.2" +boto3 = "^1.33.6" charset-normalizer = "^3.1.0" click = "*" colorama = "^0.4.6" @@ -68,6 +69,7 @@ openapi-python-client = "^0.14.0" # agbenchmark = { path = "../../benchmark", optional = true } agbenchmark = {git = "https://github.com/Significant-Gravitas/AutoGPT.git", subdirectory = "benchmark", optional = true} google-cloud-logging = "^3.8.0" +google-cloud-storage = "^2.13.0" [tool.poetry.extras] benchmark = ["agbenchmark"] @@ -75,6 +77,7 @@ benchmark = ["agbenchmark"] [tool.poetry.group.dev.dependencies] auto-gpt-plugin-template = {git = "https://github.com/Significant-Gravitas/Auto-GPT-Plugin-Template", rev = "0.1.0"} black = "*" +boto3-stubs = {extras = ["s3"], version = "^1.33.6"} flake8 = "*" gitpython = "^3.1.32" isort = "*" diff --git a/autogpts/autogpt/tests/conftest.py b/autogpts/autogpt/tests/conftest.py index 69852d6cf7..3aabf2e82a 100644 --- a/autogpts/autogpt/tests/conftest.py +++ b/autogpts/autogpt/tests/conftest.py @@ -11,7 +11,11 @@ from autogpt.agents.agent import Agent, AgentConfiguration, AgentSettings from autogpt.app.main import _configure_openai_provider from autogpt.config import AIProfile, Config, ConfigBuilder from autogpt.core.resource.model_providers import ChatModelProvider, OpenAIProvider -from autogpt.file_workspace import FileWorkspace +from autogpt.file_workspace.local import ( + FileWorkspace, + FileWorkspaceConfiguration, + LocalFileWorkspace, +) from autogpt.llm.api_manager import ApiManager from autogpt.logs.config import configure_logging from autogpt.models.command_registry import CommandRegistry @@ -47,7 +51,7 @@ def workspace_root(agent_data_dir: Path) -> Path: @pytest.fixture() def workspace(workspace_root: Path) -> FileWorkspace: - workspace = FileWorkspace(workspace_root, restrict_to_root=True) + workspace = LocalFileWorkspace(FileWorkspaceConfiguration(root=workspace_root)) workspace.initialize() return workspace diff --git a/autogpts/autogpt/tests/unit/test_gcs_file_workspace.py b/autogpts/autogpt/tests/unit/test_gcs_file_workspace.py new file mode 100644 index 0000000000..4fe441c23a --- /dev/null +++ b/autogpts/autogpt/tests/unit/test_gcs_file_workspace.py @@ -0,0 +1,108 @@ +import os +import uuid +from pathlib import Path + +import pytest +import pytest_asyncio +from google.cloud.exceptions import NotFound + +from autogpt.file_workspace.gcs import GCSFileWorkspace, GCSFileWorkspaceConfiguration + +if not os.getenv("GOOGLE_APPLICATION_CREDENTIALS"): + pytest.skip("GOOGLE_APPLICATION_CREDENTIALS are not set", allow_module_level=True) + + +@pytest.fixture +def gcs_bucket_name() -> str: + return f"test-bucket-{str(uuid.uuid4())[:8]}" + + +@pytest.fixture +def gcs_workspace_uninitialized(gcs_bucket_name: str) -> GCSFileWorkspace: + os.environ["WORKSPACE_STORAGE_BUCKET"] = gcs_bucket_name + ws_config = GCSFileWorkspaceConfiguration.from_env() + workspace = GCSFileWorkspace(ws_config) + yield workspace # type: ignore + del os.environ["WORKSPACE_STORAGE_BUCKET"] + + +def test_initialize( + gcs_bucket_name: str, gcs_workspace_uninitialized: GCSFileWorkspace +): + gcs = gcs_workspace_uninitialized._bucket + + # test that the bucket doesn't exist yet + with pytest.raises(NotFound): + gcs.get_blob(gcs_bucket_name) + + gcs_workspace_uninitialized.initialize() + + # test that the bucket has been created + gcs.get_blob(gcs_bucket_name) + + +def test_workspace_bucket_name( + gcs_workspace: GCSFileWorkspace, + gcs_bucket_name: str, +): + assert gcs_workspace._bucket.name == gcs_bucket_name + + +@pytest.fixture +def gcs_workspace(gcs_workspace_uninitialized: GCSFileWorkspace) -> GCSFileWorkspace: + (gcs_workspace := gcs_workspace_uninitialized).initialize() + yield gcs_workspace # type: ignore + + # Empty & delete the test bucket + gcs_workspace._bucket.delete_blobs(gcs_workspace._bucket.list_blobs()) + gcs_workspace._bucket.delete() + + +TEST_FILES: list[tuple[str | Path, str]] = [ + ("existing_test_file_1", "test content 1"), + ("existing_test_file_2.txt", "test content 2"), + (Path("existing_test_file_3"), "test content 3"), + (Path("existing/test/file/4"), "test content 4"), +] + + +@pytest_asyncio.fixture +async def gcs_workspace_with_files(gcs_workspace: GCSFileWorkspace) -> GCSFileWorkspace: + for file_name, file_content in TEST_FILES: + gcs_workspace._bucket.blob(str(file_name)).upload_from_string(file_content) + yield gcs_workspace # type: ignore + + +@pytest.mark.asyncio +async def test_read_file(gcs_workspace_with_files: GCSFileWorkspace): + for file_name, file_content in TEST_FILES: + content = gcs_workspace_with_files.read_file(file_name) + assert content == file_content + + with pytest.raises(NotFound): + gcs_workspace_with_files.read_file("non_existent_file") + + +def test_list_files(gcs_workspace_with_files: GCSFileWorkspace): + files = gcs_workspace_with_files.list_files() + assert set(files) == set(Path(file_name) for file_name, _ in TEST_FILES) + + +@pytest.mark.asyncio +async def test_write_read_file(gcs_workspace: GCSFileWorkspace): + await gcs_workspace.write_file("test_file", "test_content") + assert gcs_workspace.read_file("test_file") == "test_content" + + +@pytest.mark.asyncio +async def test_overwrite_file(gcs_workspace_with_files: GCSFileWorkspace): + for file_name, _ in TEST_FILES: + await gcs_workspace_with_files.write_file(file_name, "new content") + assert gcs_workspace_with_files.read_file(file_name) == "new content" + + +def test_delete_file(gcs_workspace_with_files: GCSFileWorkspace): + for file_to_delete, _ in TEST_FILES: + gcs_workspace_with_files.delete_file(file_to_delete) + with pytest.raises(NotFound): + gcs_workspace_with_files.read_file(file_to_delete) diff --git a/autogpts/autogpt/tests/unit/test_workspace.py b/autogpts/autogpt/tests/unit/test_local_file_workspace.py similarity index 76% rename from autogpts/autogpt/tests/unit/test_workspace.py rename to autogpts/autogpt/tests/unit/test_local_file_workspace.py index 58cad45973..8f57d43b06 100644 --- a/autogpts/autogpt/tests/unit/test_workspace.py +++ b/autogpts/autogpt/tests/unit/test_local_file_workspace.py @@ -1,9 +1,8 @@ -import itertools from pathlib import Path import pytest -from autogpt.file_workspace import FileWorkspace +from autogpt.file_workspace.local import FileWorkspaceConfiguration, LocalFileWorkspace _WORKSPACE_ROOT = Path("home/users/monty/auto_gpt_workspace") @@ -31,17 +30,11 @@ _INACCESSIBLE_PATHS = ( Path("test_folder/../../not_auto_gpt_workspace/test_file.txt"), ] + [ - # Contains null bytes - Path(template.format(null_byte=null_byte)) - for template, null_byte in itertools.product( - [ - "{null_byte}", - "{null_byte}test_file.txt", - "test_folder/{null_byte}", - "test_folder/{null_byte}test_file.txt", - ], - FileWorkspace.NULL_BYTES, - ) + # Contains null byte + Path("\0"), + Path("\0test_file.txt"), + Path("test_folder/\0"), + Path("test_folder/\0test_file.txt"), ] + [ # Absolute paths @@ -68,7 +61,7 @@ def inaccessible_path(request): def test_sanitize_path_accessible(accessible_path, workspace_root): - full_path = FileWorkspace._sanitize_path( + full_path = LocalFileWorkspace._sanitize_path( accessible_path, root=workspace_root, restrict_to_root=True, @@ -79,7 +72,7 @@ def test_sanitize_path_accessible(accessible_path, workspace_root): def test_sanitize_path_inaccessible(inaccessible_path, workspace_root): with pytest.raises(ValueError): - FileWorkspace._sanitize_path( + LocalFileWorkspace._sanitize_path( inaccessible_path, root=workspace_root, restrict_to_root=True, @@ -87,13 +80,13 @@ def test_sanitize_path_inaccessible(inaccessible_path, workspace_root): def test_get_path_accessible(accessible_path, workspace_root): - workspace = FileWorkspace(workspace_root, True) + workspace = LocalFileWorkspace(FileWorkspaceConfiguration(root=workspace_root)) full_path = workspace.get_path(accessible_path) assert full_path.is_absolute() assert full_path.is_relative_to(workspace_root) def test_get_path_inaccessible(inaccessible_path, workspace_root): - workspace = FileWorkspace(workspace_root, True) + workspace = LocalFileWorkspace(FileWorkspaceConfiguration(root=workspace_root)) with pytest.raises(ValueError): workspace.get_path(inaccessible_path) diff --git a/autogpts/autogpt/tests/unit/test_s3_file_workspace.py b/autogpts/autogpt/tests/unit/test_s3_file_workspace.py new file mode 100644 index 0000000000..8517e2749d --- /dev/null +++ b/autogpts/autogpt/tests/unit/test_s3_file_workspace.py @@ -0,0 +1,106 @@ +import os +import uuid +from pathlib import Path + +import pytest +import pytest_asyncio +from botocore.exceptions import ClientError + +from autogpt.file_workspace.s3 import S3FileWorkspace, S3FileWorkspaceConfiguration + +if not os.getenv("S3_ENDPOINT_URL") and not os.getenv("AWS_ACCESS_KEY_ID"): + pytest.skip("S3 environment variables are not set", allow_module_level=True) + + +@pytest.fixture +def s3_bucket_name() -> str: + return f"test-bucket-{str(uuid.uuid4())[:8]}" + + +@pytest.fixture +def s3_workspace_uninitialized(s3_bucket_name: str) -> S3FileWorkspace: + os.environ["WORKSPACE_STORAGE_BUCKET"] = s3_bucket_name + ws_config = S3FileWorkspaceConfiguration.from_env() + workspace = S3FileWorkspace(ws_config) + yield workspace # type: ignore + del os.environ["WORKSPACE_STORAGE_BUCKET"] + + +def test_initialize(s3_bucket_name: str, s3_workspace_uninitialized: S3FileWorkspace): + s3 = s3_workspace_uninitialized._s3 + + # test that the bucket doesn't exist yet + with pytest.raises(ClientError): + s3.meta.client.head_bucket(Bucket=s3_bucket_name) + + s3_workspace_uninitialized.initialize() + + # test that the bucket has been created + s3.meta.client.head_bucket(Bucket=s3_bucket_name) + + +def test_workspace_bucket_name( + s3_workspace: S3FileWorkspace, + s3_bucket_name: str, +): + assert s3_workspace._bucket.name == s3_bucket_name + + +@pytest.fixture +def s3_workspace(s3_workspace_uninitialized: S3FileWorkspace) -> S3FileWorkspace: + (s3_workspace := s3_workspace_uninitialized).initialize() + yield s3_workspace # type: ignore + + # Empty & delete the test bucket + s3_workspace._bucket.objects.all().delete() + s3_workspace._bucket.delete() + + +TEST_FILES: list[tuple[str | Path, str]] = [ + ("existing_test_file_1", "test content 1"), + ("existing_test_file_2.txt", "test content 2"), + (Path("existing_test_file_3"), "test content 3"), + (Path("existing/test/file/4"), "test content 4"), +] + + +@pytest_asyncio.fixture +async def s3_workspace_with_files(s3_workspace: S3FileWorkspace) -> S3FileWorkspace: + for file_name, file_content in TEST_FILES: + s3_workspace._bucket.Object(str(file_name)).put(Body=file_content) + yield s3_workspace # type: ignore + + +@pytest.mark.asyncio +async def test_read_file(s3_workspace_with_files: S3FileWorkspace): + for file_name, file_content in TEST_FILES: + content = s3_workspace_with_files.read_file(file_name) + assert content == file_content + + with pytest.raises(ClientError): + s3_workspace_with_files.read_file("non_existent_file") + + +def test_list_files(s3_workspace_with_files: S3FileWorkspace): + files = s3_workspace_with_files.list_files() + assert set(files) == set(Path(file_name) for file_name, _ in TEST_FILES) + + +@pytest.mark.asyncio +async def test_write_read_file(s3_workspace: S3FileWorkspace): + await s3_workspace.write_file("test_file", "test_content") + assert s3_workspace.read_file("test_file") == "test_content" + + +@pytest.mark.asyncio +async def test_overwrite_file(s3_workspace_with_files: S3FileWorkspace): + for file_name, _ in TEST_FILES: + await s3_workspace_with_files.write_file(file_name, "new content") + assert s3_workspace_with_files.read_file(file_name) == "new content" + + +def test_delete_file(s3_workspace_with_files: S3FileWorkspace): + for file_to_delete, _ in TEST_FILES: + s3_workspace_with_files.delete_file(file_to_delete) + with pytest.raises(ClientError): + s3_workspace_with_files.read_file(file_to_delete)