diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 68b6fc2b7c27..18191139f72e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,6 +7,9 @@ updates: interval: "weekly" open-pull-requests-limit: 10 target-branch: "dev" + commit-message: + prefix: "chore(libs/deps)" + prefix-development: "chore(libs/deps-dev)" groups: production-dependencies: dependency-type: "production" @@ -26,6 +29,9 @@ updates: interval: "weekly" open-pull-requests-limit: 10 target-branch: "dev" + commit-message: + prefix: "chore(backend/deps)" + prefix-development: "chore(backend/deps-dev)" groups: production-dependencies: dependency-type: "production" @@ -38,7 +44,6 @@ updates: - "minor" - "patch" - # frontend (Next.js project) - package-ecosystem: "npm" directory: "autogpt_platform/frontend" @@ -46,6 +51,9 @@ updates: interval: "weekly" open-pull-requests-limit: 10 target-branch: "dev" + commit-message: + prefix: "chore(frontend/deps)" + prefix-development: "chore(frontend/deps-dev)" groups: production-dependencies: dependency-type: "production" @@ -58,7 +66,6 @@ updates: - "minor" - "patch" - # infra (Terraform) - package-ecosystem: "terraform" directory: "autogpt_platform/infra" @@ -66,6 +73,10 @@ updates: interval: "weekly" open-pull-requests-limit: 5 target-branch: "dev" + commit-message: + prefix: "chore(infra/deps)" + prefix-development: "chore(infra/deps-dev)" + groups: production-dependencies: dependency-type: "production" @@ -78,7 +89,6 @@ updates: - "minor" - "patch" - # market (Poetry project) - package-ecosystem: "pip" directory: "autogpt_platform/market" @@ -86,6 +96,9 @@ updates: interval: "weekly" open-pull-requests-limit: 10 target-branch: "dev" + commit-message: + prefix: "chore(market/deps)" + prefix-development: "chore(market/deps-dev)" groups: production-dependencies: dependency-type: "production" @@ -146,6 +159,9 @@ updates: interval: "weekly" open-pull-requests-limit: 1 target-branch: "dev" + commit-message: + prefix: "chore(platform/deps)" + prefix-development: "chore(platform/deps-dev)" groups: production-dependencies: dependency-type: "production" @@ -166,6 +182,8 @@ updates: interval: "weekly" open-pull-requests-limit: 1 target-branch: "dev" + commit-message: + prefix: "chore(docs/deps)" groups: production-dependencies: dependency-type: "production" diff --git a/.github/workflows/classic-autogpt-docker-cache-clean.yml b/.github/workflows/classic-autogpt-docker-cache-clean.yml index f550a84ce387..1a91e946cf18 100644 --- a/.github/workflows/classic-autogpt-docker-cache-clean.yml +++ b/.github/workflows/classic-autogpt-docker-cache-clean.yml @@ -23,7 +23,7 @@ jobs: - id: build name: Build image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: classic/ file: classic/Dockerfile.autogpt diff --git a/.github/workflows/classic-autogpt-docker-ci.yml b/.github/workflows/classic-autogpt-docker-ci.yml index 914b157c8d52..4dc955a5b935 100644 --- a/.github/workflows/classic-autogpt-docker-ci.yml +++ b/.github/workflows/classic-autogpt-docker-ci.yml @@ -47,7 +47,7 @@ jobs: - id: build name: Build image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: classic/ file: classic/Dockerfile.autogpt @@ -117,7 +117,7 @@ jobs: - id: build name: Build image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: classic/ file: classic/Dockerfile.autogpt diff --git a/.github/workflows/classic-autogpt-docker-release.yml b/.github/workflows/classic-autogpt-docker-release.yml index 9397c6e3dde7..fd333395400c 100644 --- a/.github/workflows/classic-autogpt-docker-release.yml +++ b/.github/workflows/classic-autogpt-docker-release.yml @@ -42,7 +42,7 @@ jobs: - id: build name: Build image - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 with: context: classic/ file: Dockerfile.autogpt diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 63065c65f11d..a6c36ed86c54 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -16,6 +16,7 @@ on: branches: [ "master", "release-*", "dev" ] pull_request: branches: [ "master", "release-*", "dev" ] + merge_group: schedule: - cron: '15 4 * * 0' diff --git a/.github/workflows/platform-backend-ci.yml b/.github/workflows/platform-backend-ci.yml index 63fe8b118f6c..8b168044c18d 100644 --- a/.github/workflows/platform-backend-ci.yml +++ b/.github/workflows/platform-backend-ci.yml @@ -11,6 +11,7 @@ on: paths: - ".github/workflows/platform-backend-ci.yml" - "autogpt_platform/backend/**" + merge_group: concurrency: group: ${{ format('backend-ci-{0}', github.head_ref && format('{0}-{1}', github.event_name, github.event.pull_request.number) || github.sha) }} diff --git a/.github/workflows/platform-frontend-ci.yml b/.github/workflows/platform-frontend-ci.yml index 7c1f792917f0..a1f68432bb03 100644 --- a/.github/workflows/platform-frontend-ci.yml +++ b/.github/workflows/platform-frontend-ci.yml @@ -10,6 +10,7 @@ on: paths: - ".github/workflows/platform-frontend-ci.yml" - "autogpt_platform/frontend/**" + merge_group: defaults: run: diff --git a/.github/workflows/platform-market-ci.yml b/.github/workflows/platform-market-ci.yml index 560c05d64ae6..c4ba56f9632c 100644 --- a/.github/workflows/platform-market-ci.yml +++ b/.github/workflows/platform-market-ci.yml @@ -11,6 +11,7 @@ on: paths: - ".github/workflows/platform-market-ci.yml" - "autogpt_platform/market/**" + merge_group: concurrency: group: ${{ format('backend-ci-{0}', github.head_ref && format('{0}-{1}', github.event_name, github.event.pull_request.number) || github.sha) }} diff --git a/.github/workflows/repo-workflow-checker.yml b/.github/workflows/repo-workflow-checker.yml index d48e8c001990..35536ba92215 100644 --- a/.github/workflows/repo-workflow-checker.yml +++ b/.github/workflows/repo-workflow-checker.yml @@ -2,6 +2,7 @@ name: Repo - PR Status Checker on: pull_request: types: [opened, synchronize, reopened] + merge_group: jobs: status-check: diff --git a/.github/workflows/scripts/check_actions_status.py b/.github/workflows/scripts/check_actions_status.py index 82f6d6c58cba..37f83da45271 100644 --- a/.github/workflows/scripts/check_actions_status.py +++ b/.github/workflows/scripts/check_actions_status.py @@ -7,13 +7,18 @@ CHECK_INTERVAL = 30 + def get_environment_variables() -> Tuple[str, str, str, str, str]: """Retrieve and return necessary environment variables.""" try: with open(os.environ["GITHUB_EVENT_PATH"]) as f: event = json.load(f) - sha = event["pull_request"]["head"]["sha"] + # Handle both PR and merge group events + if "pull_request" in event: + sha = event["pull_request"]["head"]["sha"] + else: + sha = os.environ["GITHUB_SHA"] return ( os.environ["GITHUB_API_URL"], diff --git a/.gitignore b/.gitignore index 6590e212993c..225a4b93b029 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,5 @@ ig* .github_access_token LICENSE.rtf autogpt_platform/backend/settings.py +/.auth +/autogpt_platform/frontend/.auth diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py b/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py index 637d5da3531f..257250a75393 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/api_key/key_manager.py @@ -1,14 +1,17 @@ -from typing import NamedTuple -import secrets import hashlib +import secrets +from typing import NamedTuple + class APIKeyContainer(NamedTuple): """Container for API key parts.""" + raw: str prefix: str postfix: str hash: str + class APIKeyManager: PREFIX: str = "agpt_" PREFIX_LENGTH: int = 8 @@ -19,9 +22,9 @@ def generate_api_key(self) -> APIKeyContainer: raw_key = f"{self.PREFIX}{secrets.token_urlsafe(32)}" return APIKeyContainer( raw=raw_key, - prefix=raw_key[:self.PREFIX_LENGTH], - postfix=raw_key[-self.POSTFIX_LENGTH:], - hash=hashlib.sha256(raw_key.encode()).hexdigest() + prefix=raw_key[: self.PREFIX_LENGTH], + postfix=raw_key[-self.POSTFIX_LENGTH :], + hash=hashlib.sha256(raw_key.encode()).hexdigest(), ) def verify_api_key(self, provided_key: str, stored_hash: str) -> bool: diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/auth/depends.py b/autogpt_platform/autogpt_libs/autogpt_libs/auth/depends.py index fa1a385011b2..55fa4273665a 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/auth/depends.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/auth/depends.py @@ -1,8 +1,8 @@ import fastapi -from .middleware import auth_middleware -from .models import User, DEFAULT_USER_ID, DEFAULT_EMAIL from .config import Settings +from .middleware import auth_middleware +from .models import DEFAULT_USER_ID, User def requires_user(payload: dict = fastapi.Depends(auth_middleware)) -> User: diff --git a/autogpt_platform/backend/backend/data/__init__.py b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/__init__.py similarity index 100% rename from autogpt_platform/backend/backend/data/__init__.py rename to autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/__init__.py diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client.py b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client.py new file mode 100644 index 000000000000..d9f081e5cb4b --- /dev/null +++ b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client.py @@ -0,0 +1,167 @@ +import asyncio +import contextlib +import logging +from functools import wraps +from typing import Any, Awaitable, Callable, Dict, Optional, TypeVar, Union, cast + +import ldclient +from fastapi import HTTPException +from ldclient import Context, LDClient +from ldclient.config import Config +from typing_extensions import ParamSpec + +from .config import SETTINGS + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.DEBUG) + +P = ParamSpec("P") +T = TypeVar("T") + + +def get_client() -> LDClient: + """Get the LaunchDarkly client singleton.""" + return ldclient.get() + + +def initialize_launchdarkly() -> None: + sdk_key = SETTINGS.launch_darkly_sdk_key + logger.debug( + f"Initializing LaunchDarkly with SDK key: {'present' if sdk_key else 'missing'}" + ) + + if not sdk_key: + logger.warning("LaunchDarkly SDK key not configured") + return + + config = Config(sdk_key) + ldclient.set_config(config) + + if ldclient.get().is_initialized(): + logger.info("LaunchDarkly client initialized successfully") + else: + logger.error("LaunchDarkly client failed to initialize") + + +def shutdown_launchdarkly() -> None: + """Shutdown the LaunchDarkly client.""" + if ldclient.get().is_initialized(): + ldclient.get().close() + logger.info("LaunchDarkly client closed successfully") + + +def create_context( + user_id: str, additional_attributes: Optional[Dict[str, Any]] = None +) -> Context: + """Create LaunchDarkly context with optional additional attributes.""" + builder = Context.builder(str(user_id)).kind("user") + if additional_attributes: + for key, value in additional_attributes.items(): + builder.set(key, value) + return builder.build() + + +def feature_flag( + flag_key: str, + default: bool = False, +) -> Callable[ + [Callable[P, Union[T, Awaitable[T]]]], Callable[P, Union[T, Awaitable[T]]] +]: + """ + Decorator for feature flag protected endpoints. + """ + + def decorator( + func: Callable[P, Union[T, Awaitable[T]]] + ) -> Callable[P, Union[T, Awaitable[T]]]: + @wraps(func) + async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + try: + user_id = kwargs.get("user_id") + if not user_id: + raise ValueError("user_id is required") + + if not get_client().is_initialized(): + logger.warning( + f"LaunchDarkly not initialized, using default={default}" + ) + is_enabled = default + else: + context = create_context(str(user_id)) + is_enabled = get_client().variation(flag_key, context, default) + + if not is_enabled: + raise HTTPException(status_code=404, detail="Feature not available") + + result = func(*args, **kwargs) + if asyncio.iscoroutine(result): + return await result + return cast(T, result) + except Exception as e: + logger.error(f"Error evaluating feature flag {flag_key}: {e}") + raise + + @wraps(func) + def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T: + try: + user_id = kwargs.get("user_id") + if not user_id: + raise ValueError("user_id is required") + + if not get_client().is_initialized(): + logger.warning( + f"LaunchDarkly not initialized, using default={default}" + ) + is_enabled = default + else: + context = create_context(str(user_id)) + is_enabled = get_client().variation(flag_key, context, default) + + if not is_enabled: + raise HTTPException(status_code=404, detail="Feature not available") + + return cast(T, func(*args, **kwargs)) + except Exception as e: + logger.error(f"Error evaluating feature flag {flag_key}: {e}") + raise + + return cast( + Callable[P, Union[T, Awaitable[T]]], + async_wrapper if asyncio.iscoroutinefunction(func) else sync_wrapper, + ) + + return decorator + + +def percentage_rollout( + flag_key: str, + default: bool = False, +) -> Callable[ + [Callable[P, Union[T, Awaitable[T]]]], Callable[P, Union[T, Awaitable[T]]] +]: + """Decorator for percentage-based rollouts.""" + return feature_flag(flag_key, default) + + +def beta_feature( + flag_key: Optional[str] = None, + unauthorized_response: Any = {"message": "Not available in beta"}, +) -> Callable[ + [Callable[P, Union[T, Awaitable[T]]]], Callable[P, Union[T, Awaitable[T]]] +]: + """Decorator for beta features.""" + actual_key = f"beta-{flag_key}" if flag_key else "beta" + return feature_flag(actual_key, False) + + +@contextlib.contextmanager +def mock_flag_variation(flag_key: str, return_value: Any): + """Context manager for testing feature flags.""" + original_variation = get_client().variation + get_client().variation = lambda key, context, default: ( + return_value if key == flag_key else original_variation(key, context, default) + ) + try: + yield + finally: + get_client().variation = original_variation diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client_test.py b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client_test.py new file mode 100644 index 000000000000..8fccfb28b501 --- /dev/null +++ b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/client_test.py @@ -0,0 +1,45 @@ +import pytest +from ldclient import LDClient + +from autogpt_libs.feature_flag.client import feature_flag, mock_flag_variation + + +@pytest.fixture +def ld_client(mocker): + client = mocker.Mock(spec=LDClient) + mocker.patch("ldclient.get", return_value=client) + client.is_initialized.return_value = True + return client + + +@pytest.mark.asyncio +async def test_feature_flag_enabled(ld_client): + ld_client.variation.return_value = True + + @feature_flag("test-flag") + async def test_function(user_id: str): + return "success" + + result = test_function(user_id="test-user") + assert result == "success" + ld_client.variation.assert_called_once() + + +@pytest.mark.asyncio +async def test_feature_flag_unauthorized_response(ld_client): + ld_client.variation.return_value = False + + @feature_flag("test-flag") + async def test_function(user_id: str): + return "success" + + result = test_function(user_id="test-user") + assert result == {"error": "disabled"} + + +def test_mock_flag_variation(ld_client): + with mock_flag_variation("test-flag", True): + assert ld_client.variation("test-flag", None, False) + + with mock_flag_variation("test-flag", False): + assert ld_client.variation("test-flag", None, False) diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/config.py b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/config.py new file mode 100644 index 000000000000..e01c285d1e66 --- /dev/null +++ b/autogpt_platform/autogpt_libs/autogpt_libs/feature_flag/config.py @@ -0,0 +1,15 @@ +from pydantic import Field +from pydantic_settings import BaseSettings, SettingsConfigDict + + +class Settings(BaseSettings): + launch_darkly_sdk_key: str = Field( + default="", + description="The Launch Darkly SDK key", + validation_alias="LAUNCH_DARKLY_SDK_KEY", + ) + + model_config = SettingsConfigDict(case_sensitive=True, extra="ignore") + + +SETTINGS = Settings() diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/logging/config.py b/autogpt_platform/autogpt_libs/autogpt_libs/logging/config.py index 29df3d10f411..523f6cf8ec87 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/logging/config.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/logging/config.py @@ -6,6 +6,7 @@ from pydantic import Field, field_validator from pydantic_settings import BaseSettings, SettingsConfigDict + from .filters import BelowLevelFilter from .formatters import AGPTFormatter, StructuredLoggingFormatter diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/__init__.py b/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/__init__.py deleted file mode 100644 index f957198eb776..000000000000 --- a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -from .store import SupabaseIntegrationCredentialsStore -from .types import Credentials, APIKeyCredentials, OAuth2Credentials - -__all__ = [ - "SupabaseIntegrationCredentialsStore", - "Credentials", - "APIKeyCredentials", - "OAuth2Credentials", -] diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/types.py b/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/types.py deleted file mode 100644 index 384dfdc79729..000000000000 --- a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/types.py +++ /dev/null @@ -1,75 +0,0 @@ -from typing import Annotated, Any, Literal, Optional, TypedDict -from uuid import uuid4 - -from pydantic import BaseModel, Field, SecretStr, field_serializer - - -class _BaseCredentials(BaseModel): - id: str = Field(default_factory=lambda: str(uuid4())) - provider: str - title: Optional[str] - - @field_serializer("*") - def dump_secret_strings(value: Any, _info): - if isinstance(value, SecretStr): - return value.get_secret_value() - return value - - -class OAuth2Credentials(_BaseCredentials): - type: Literal["oauth2"] = "oauth2" - username: Optional[str] - """Username of the third-party service user that these credentials belong to""" - access_token: SecretStr - access_token_expires_at: Optional[int] - """Unix timestamp (seconds) indicating when the access token expires (if at all)""" - refresh_token: Optional[SecretStr] - refresh_token_expires_at: Optional[int] - """Unix timestamp (seconds) indicating when the refresh token expires (if at all)""" - scopes: list[str] - metadata: dict[str, Any] = Field(default_factory=dict) - - def bearer(self) -> str: - return f"Bearer {self.access_token.get_secret_value()}" - - -class APIKeyCredentials(_BaseCredentials): - type: Literal["api_key"] = "api_key" - api_key: SecretStr - expires_at: Optional[int] - """Unix timestamp (seconds) indicating when the API key expires (if at all)""" - - def bearer(self) -> str: - return f"Bearer {self.api_key.get_secret_value()}" - - -Credentials = Annotated[ - OAuth2Credentials | APIKeyCredentials, - Field(discriminator="type"), -] - - -CredentialsType = Literal["api_key", "oauth2"] - - -class OAuthState(BaseModel): - token: str - provider: str - expires_at: int - scopes: list[str] - """Unix timestamp (seconds) indicating when this OAuth state expires""" - - -class UserMetadata(BaseModel): - integration_credentials: list[Credentials] = Field(default_factory=list) - integration_oauth_states: list[OAuthState] = Field(default_factory=list) - - -class UserMetadataRaw(TypedDict, total=False): - integration_credentials: list[dict] - integration_oauth_states: list[dict] - - -class UserIntegrations(BaseModel): - credentials: list[Credentials] = Field(default_factory=list) - oauth_states: list[OAuthState] = Field(default_factory=list) diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/utils/__init__.py b/autogpt_platform/autogpt_libs/autogpt_libs/utils/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/utils/cache.py b/autogpt_platform/autogpt_libs/autogpt_libs/utils/cache.py index 9c69da9411e2..5c0241c258cb 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/utils/cache.py +++ b/autogpt_platform/autogpt_libs/autogpt_libs/utils/cache.py @@ -1,5 +1,5 @@ -from typing import Callable, TypeVar, ParamSpec import threading +from typing import Callable, ParamSpec, TypeVar P = ParamSpec("P") R = TypeVar("R") diff --git a/autogpt_platform/autogpt_libs/poetry.lock b/autogpt_platform/autogpt_libs/poetry.lock index 21493f743338..01dc74f26851 100644 --- a/autogpt_platform/autogpt_libs/poetry.lock +++ b/autogpt_platform/autogpt_libs/poetry.lock @@ -854,6 +854,17 @@ doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linke perf = ["ipython"] test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + [[package]] name = "multidict" version = "6.1.0" @@ -984,6 +995,21 @@ files = [ {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + [[package]] name = "postgrest" version = "0.18.0" @@ -1065,22 +1091,19 @@ pyasn1 = ">=0.4.6,<0.7.0" [[package]] name = "pydantic" -version = "2.9.2" +version = "2.10.2" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.9.2-py3-none-any.whl", hash = "sha256:f048cec7b26778210e28a0459867920654d48e5e62db0958433636cde4254f12"}, - {file = "pydantic-2.9.2.tar.gz", hash = "sha256:d155cef71265d1e9807ed1c32b4c8deec042a44a50a4188b25ac67ecd81a9c0f"}, + {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, + {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, ] [package.dependencies] annotated-types = ">=0.6.0" -pydantic-core = "2.23.4" -typing-extensions = [ - {version = ">=4.12.2", markers = "python_version >= \"3.13\""}, - {version = ">=4.6.1", markers = "python_version < \"3.13\""}, -] +pydantic-core = "2.27.1" +typing-extensions = ">=4.12.2" [package.extras] email = ["email-validator (>=2.0.0)"] @@ -1088,100 +1111,111 @@ timezone = ["tzdata"] [[package]] name = "pydantic-core" -version = "2.23.4" +version = "2.27.1" description = "Core functionality for Pydantic validation and serialization" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b10bd51f823d891193d4717448fab065733958bdb6a6b351967bd349d48d5c9b"}, - {file = "pydantic_core-2.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4fc714bdbfb534f94034efaa6eadd74e5b93c8fa6315565a222f7b6f42ca1166"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63e46b3169866bd62849936de036f901a9356e36376079b05efa83caeaa02ceb"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed1a53de42fbe34853ba90513cea21673481cd81ed1be739f7f2efb931b24916"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cfdd16ab5e59fc31b5e906d1a3f666571abc367598e3e02c83403acabc092e07"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:255a8ef062cbf6674450e668482456abac99a5583bbafb73f9ad469540a3a232"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a7cd62e831afe623fbb7aabbb4fe583212115b3ef38a9f6b71869ba644624a2"}, - {file = "pydantic_core-2.23.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f09e2ff1f17c2b51f2bc76d1cc33da96298f0a036a137f5440ab3ec5360b624f"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e38e63e6f3d1cec5a27e0afe90a085af8b6806ee208b33030e65b6516353f1a3"}, - {file = "pydantic_core-2.23.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0dbd8dbed2085ed23b5c04afa29d8fd2771674223135dc9bc937f3c09284d071"}, - {file = "pydantic_core-2.23.4-cp310-none-win32.whl", hash = "sha256:6531b7ca5f951d663c339002e91aaebda765ec7d61b7d1e3991051906ddde119"}, - {file = "pydantic_core-2.23.4-cp310-none-win_amd64.whl", hash = "sha256:7c9129eb40958b3d4500fa2467e6a83356b3b61bfff1b414c7361d9220f9ae8f"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:77733e3892bb0a7fa797826361ce8a9184d25c8dffaec60b7ffe928153680ba8"}, - {file = "pydantic_core-2.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b84d168f6c48fabd1f2027a3d1bdfe62f92cade1fb273a5d68e621da0e44e6d"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df49e7a0861a8c36d089c1ed57d308623d60416dab2647a4a17fe050ba85de0e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ff02b6d461a6de369f07ec15e465a88895f3223eb75073ffea56b84d9331f607"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:996a38a83508c54c78a5f41456b0103c30508fed9abcad0a59b876d7398f25fd"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d97683ddee4723ae8c95d1eddac7c192e8c552da0c73a925a89fa8649bf13eea"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:216f9b2d7713eb98cb83c80b9c794de1f6b7e3145eef40400c62e86cee5f4e1e"}, - {file = "pydantic_core-2.23.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6f783e0ec4803c787bcea93e13e9932edab72068f68ecffdf86a99fd5918878b"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d0776dea117cf5272382634bd2a5c1b6eb16767c223c6a5317cd3e2a757c61a0"}, - {file = "pydantic_core-2.23.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5f7a395a8cf1621939692dba2a6b6a830efa6b3cee787d82c7de1ad2930de64"}, - {file = "pydantic_core-2.23.4-cp311-none-win32.whl", hash = "sha256:74b9127ffea03643e998e0c5ad9bd3811d3dac8c676e47db17b0ee7c3c3bf35f"}, - {file = "pydantic_core-2.23.4-cp311-none-win_amd64.whl", hash = "sha256:98d134c954828488b153d88ba1f34e14259284f256180ce659e8d83e9c05eaa3"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f3e0da4ebaef65158d4dfd7d3678aad692f7666877df0002b8a522cdf088f231"}, - {file = "pydantic_core-2.23.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f69a8e0b033b747bb3e36a44e7732f0c99f7edd5cea723d45bc0d6e95377ffee"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723314c1d51722ab28bfcd5240d858512ffd3116449c557a1336cbe3919beb87"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bb2802e667b7051a1bebbfe93684841cc9351004e2badbd6411bf357ab8d5ac8"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d18ca8148bebe1b0a382a27a8ee60350091a6ddaf475fa05ef50dc35b5df6327"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33e3d65a85a2a4a0dc3b092b938a4062b1a05f3a9abde65ea93b233bca0e03f2"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:128585782e5bfa515c590ccee4b727fb76925dd04a98864182b22e89a4e6ed36"}, - {file = "pydantic_core-2.23.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:68665f4c17edcceecc112dfed5dbe6f92261fb9d6054b47d01bf6371a6196126"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:20152074317d9bed6b7a95ade3b7d6054845d70584216160860425f4fbd5ee9e"}, - {file = "pydantic_core-2.23.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:9261d3ce84fa1d38ed649c3638feefeae23d32ba9182963e465d58d62203bd24"}, - {file = "pydantic_core-2.23.4-cp312-none-win32.whl", hash = "sha256:4ba762ed58e8d68657fc1281e9bb72e1c3e79cc5d464be146e260c541ec12d84"}, - {file = "pydantic_core-2.23.4-cp312-none-win_amd64.whl", hash = "sha256:97df63000f4fea395b2824da80e169731088656d1818a11b95f3b173747b6cd9"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:7530e201d10d7d14abce4fb54cfe5b94a0aefc87da539d0346a484ead376c3cc"}, - {file = "pydantic_core-2.23.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:df933278128ea1cd77772673c73954e53a1c95a4fdf41eef97c2b779271bd0bd"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cb3da3fd1b6a5d0279a01877713dbda118a2a4fc6f0d821a57da2e464793f05"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:42c6dcb030aefb668a2b7009c85b27f90e51e6a3b4d5c9bc4c57631292015b0d"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:696dd8d674d6ce621ab9d45b205df149399e4bb9aa34102c970b721554828510"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2971bb5ffe72cc0f555c13e19b23c85b654dd2a8f7ab493c262071377bfce9f6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8394d940e5d400d04cad4f75c0598665cbb81aecefaca82ca85bd28264af7f9b"}, - {file = "pydantic_core-2.23.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0dff76e0602ca7d4cdaacc1ac4c005e0ce0dcfe095d5b5259163a80d3a10d327"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:7d32706badfe136888bdea71c0def994644e09fff0bfe47441deaed8e96fdbc6"}, - {file = "pydantic_core-2.23.4-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed541d70698978a20eb63d8c5d72f2cc6d7079d9d90f6b50bad07826f1320f5f"}, - {file = "pydantic_core-2.23.4-cp313-none-win32.whl", hash = "sha256:3d5639516376dce1940ea36edf408c554475369f5da2abd45d44621cb616f769"}, - {file = "pydantic_core-2.23.4-cp313-none-win_amd64.whl", hash = "sha256:5a1504ad17ba4210df3a045132a7baeeba5a200e930f57512ee02909fc5c4cb5"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:d4488a93b071c04dc20f5cecc3631fc78b9789dd72483ba15d423b5b3689b555"}, - {file = "pydantic_core-2.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:81965a16b675b35e1d09dd14df53f190f9129c0202356ed44ab2728b1c905658"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ffa2ebd4c8530079140dd2d7f794a9d9a73cbb8e9d59ffe24c63436efa8f271"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:61817945f2fe7d166e75fbfb28004034b48e44878177fc54d81688e7b85a3665"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:29d2c342c4bc01b88402d60189f3df065fb0dda3654744d5a165a5288a657368"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5e11661ce0fd30a6790e8bcdf263b9ec5988e95e63cf901972107efc49218b13"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9d18368b137c6295db49ce7218b1a9ba15c5bc254c96d7c9f9e924a9bc7825ad"}, - {file = "pydantic_core-2.23.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ec4e55f79b1c4ffb2eecd8a0cfba9955a2588497d96851f4c8f99aa4a1d39b12"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:374a5e5049eda9e0a44c696c7ade3ff355f06b1fe0bb945ea3cac2bc336478a2"}, - {file = "pydantic_core-2.23.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5c364564d17da23db1106787675fc7af45f2f7b58b4173bfdd105564e132e6fb"}, - {file = "pydantic_core-2.23.4-cp38-none-win32.whl", hash = "sha256:d7a80d21d613eec45e3d41eb22f8f94ddc758a6c4720842dc74c0581f54993d6"}, - {file = "pydantic_core-2.23.4-cp38-none-win_amd64.whl", hash = "sha256:5f5ff8d839f4566a474a969508fe1c5e59c31c80d9e140566f9a37bba7b8d556"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:a4fa4fc04dff799089689f4fd502ce7d59de529fc2f40a2c8836886c03e0175a"}, - {file = "pydantic_core-2.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0a7df63886be5e270da67e0966cf4afbae86069501d35c8c1b3b6c168f42cb36"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcedcd19a557e182628afa1d553c3895a9f825b936415d0dbd3cd0bbcfd29b4b"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f54b118ce5de9ac21c363d9b3caa6c800341e8c47a508787e5868c6b79c9323"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:86d2f57d3e1379a9525c5ab067b27dbb8a0642fb5d454e17a9ac434f9ce523e3"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de6d1d1b9e5101508cb37ab0d972357cac5235f5c6533d1071964c47139257df"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1278e0d324f6908e872730c9102b0112477a7f7cf88b308e4fc36ce1bdb6d58c"}, - {file = "pydantic_core-2.23.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a6b5099eeec78827553827f4c6b8615978bb4b6a88e5d9b93eddf8bb6790f55"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e55541f756f9b3ee346b840103f32779c695a19826a4c442b7954550a0972040"}, - {file = "pydantic_core-2.23.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a5c7ba8ffb6d6f8f2ab08743be203654bb1aaa8c9dcb09f82ddd34eadb695605"}, - {file = "pydantic_core-2.23.4-cp39-none-win32.whl", hash = "sha256:37b0fe330e4a58d3c58b24d91d1eb102aeec675a3db4c292ec3928ecd892a9a6"}, - {file = "pydantic_core-2.23.4-cp39-none-win_amd64.whl", hash = "sha256:1498bec4c05c9c787bde9125cfdcc63a41004ff167f495063191b863399b1a29"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f455ee30a9d61d3e1a15abd5068827773d6e4dc513e795f380cdd59932c782d5"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:1e90d2e3bd2c3863d48525d297cd143fe541be8bbf6f579504b9712cb6b643ec"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2e203fdf807ac7e12ab59ca2bfcabb38c7cf0b33c41efeb00f8e5da1d86af480"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e08277a400de01bc72436a0ccd02bdf596631411f592ad985dcee21445bd0068"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f220b0eea5965dec25480b6333c788fb72ce5f9129e8759ef876a1d805d00801"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:d06b0c8da4f16d1d1e352134427cb194a0a6e19ad5db9161bf32b2113409e728"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ba1a0996f6c2773bd83e63f18914c1de3c9dd26d55f4ac302a7efe93fb8e7433"}, - {file = "pydantic_core-2.23.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:9a5bce9d23aac8f0cf0836ecfc033896aa8443b501c58d0602dbfd5bd5b37753"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:78ddaaa81421a29574a682b3179d4cf9e6d405a09b99d93ddcf7e5239c742e21"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:883a91b5dd7d26492ff2f04f40fbb652de40fcc0afe07e8129e8ae779c2110eb"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88ad334a15b32a791ea935af224b9de1bf99bcd62fabf745d5f3442199d86d59"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:233710f069d251feb12a56da21e14cca67994eab08362207785cf8c598e74577"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:19442362866a753485ba5e4be408964644dd6a09123d9416c54cd49171f50744"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:624e278a7d29b6445e4e813af92af37820fafb6dcc55c012c834f9e26f9aaaef"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f5ef8f42bec47f21d07668a043f077d507e5bf4e668d5c6dfe6aaba89de1a5b8"}, - {file = "pydantic_core-2.23.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:aea443fffa9fbe3af1a9ba721a87f926fe548d32cab71d188a6ede77d0ff244e"}, - {file = "pydantic_core-2.23.4.tar.gz", hash = "sha256:2584f7cf844ac4d970fba483a717dbe10c1c1c96a969bf65d61ffe94df1b2863"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:71a5e35c75c021aaf400ac048dacc855f000bdfed91614b4a726f7432f1f3d6a"}, + {file = "pydantic_core-2.27.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f82d068a2d6ecfc6e054726080af69a6764a10015467d7d7b9f66d6ed5afa23b"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:121ceb0e822f79163dd4699e4c54f5ad38b157084d97b34de8b232bcaad70278"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4603137322c18eaf2e06a4495f426aa8d8388940f3c457e7548145011bb68e05"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a33cd6ad9017bbeaa9ed78a2e0752c5e250eafb9534f308e7a5f7849b0b1bfb4"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15cc53a3179ba0fcefe1e3ae50beb2784dede4003ad2dfd24f81bba4b23a454f"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45d9c5eb9273aa50999ad6adc6be5e0ecea7e09dbd0d31bd0c65a55a2592ca08"}, + {file = "pydantic_core-2.27.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bf7b66ce12a2ac52d16f776b31d16d91033150266eb796967a7e4621707e4f6"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:655d7dd86f26cb15ce8a431036f66ce0318648f8853d709b4167786ec2fa4807"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_armv7l.whl", hash = "sha256:5556470f1a2157031e676f776c2bc20acd34c1990ca5f7e56f1ebf938b9ab57c"}, + {file = "pydantic_core-2.27.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f69ed81ab24d5a3bd93861c8c4436f54afdf8e8cc421562b0c7504cf3be58206"}, + {file = "pydantic_core-2.27.1-cp310-none-win32.whl", hash = "sha256:f5a823165e6d04ccea61a9f0576f345f8ce40ed533013580e087bd4d7442b52c"}, + {file = "pydantic_core-2.27.1-cp310-none-win_amd64.whl", hash = "sha256:57866a76e0b3823e0b56692d1a0bf722bffb324839bb5b7226a7dbd6c9a40b17"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac3b20653bdbe160febbea8aa6c079d3df19310d50ac314911ed8cc4eb7f8cb8"}, + {file = "pydantic_core-2.27.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a5a8e19d7c707c4cadb8c18f5f60c843052ae83c20fa7d44f41594c644a1d330"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f7059ca8d64fea7f238994c97d91f75965216bcbe5f695bb44f354893f11d52"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bed0f8a0eeea9fb72937ba118f9db0cb7e90773462af7962d382445f3005e5a4"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a3cb37038123447cf0f3ea4c74751f6a9d7afef0eb71aa07bf5f652b5e6a132c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:84286494f6c5d05243456e04223d5a9417d7f443c3b76065e75001beb26f88de"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acc07b2cfc5b835444b44a9956846b578d27beeacd4b52e45489e93276241025"}, + {file = "pydantic_core-2.27.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:4fefee876e07a6e9aad7a8c8c9f85b0cdbe7df52b8a9552307b09050f7512c7e"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:258c57abf1188926c774a4c94dd29237e77eda19462e5bb901d88adcab6af919"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:35c14ac45fcfdf7167ca76cc80b2001205a8d5d16d80524e13508371fb8cdd9c"}, + {file = "pydantic_core-2.27.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d1b26e1dff225c31897696cab7d4f0a315d4c0d9e8666dbffdb28216f3b17fdc"}, + {file = "pydantic_core-2.27.1-cp311-none-win32.whl", hash = "sha256:2cdf7d86886bc6982354862204ae3b2f7f96f21a3eb0ba5ca0ac42c7b38598b9"}, + {file = "pydantic_core-2.27.1-cp311-none-win_amd64.whl", hash = "sha256:3af385b0cee8df3746c3f406f38bcbfdc9041b5c2d5ce3e5fc6637256e60bbc5"}, + {file = "pydantic_core-2.27.1-cp311-none-win_arm64.whl", hash = "sha256:81f2ec23ddc1b476ff96563f2e8d723830b06dceae348ce02914a37cb4e74b89"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:9cbd94fc661d2bab2bc702cddd2d3370bbdcc4cd0f8f57488a81bcce90c7a54f"}, + {file = "pydantic_core-2.27.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5f8c4718cd44ec1580e180cb739713ecda2bdee1341084c1467802a417fe0f02"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15aae984e46de8d376df515f00450d1522077254ef6b7ce189b38ecee7c9677c"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:1ba5e3963344ff25fc8c40da90f44b0afca8cfd89d12964feb79ac1411a260ac"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:992cea5f4f3b29d6b4f7f1726ed8ee46c8331c6b4eed6db5b40134c6fe1768bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0325336f348dbee6550d129b1627cb8f5351a9dc91aad141ffb96d4937bd9529"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7597c07fbd11515f654d6ece3d0e4e5093edc30a436c63142d9a4b8e22f19c35"}, + {file = "pydantic_core-2.27.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3bbd5d8cc692616d5ef6fbbbd50dbec142c7e6ad9beb66b78a96e9c16729b089"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:dc61505e73298a84a2f317255fcc72b710b72980f3a1f670447a21efc88f8381"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:e1f735dc43da318cad19b4173dd1ffce1d84aafd6c9b782b3abc04a0d5a6f5bb"}, + {file = "pydantic_core-2.27.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f4e5658dbffe8843a0f12366a4c2d1c316dbe09bb4dfbdc9d2d9cd6031de8aae"}, + {file = "pydantic_core-2.27.1-cp312-none-win32.whl", hash = "sha256:672ebbe820bb37988c4d136eca2652ee114992d5d41c7e4858cdd90ea94ffe5c"}, + {file = "pydantic_core-2.27.1-cp312-none-win_amd64.whl", hash = "sha256:66ff044fd0bb1768688aecbe28b6190f6e799349221fb0de0e6f4048eca14c16"}, + {file = "pydantic_core-2.27.1-cp312-none-win_arm64.whl", hash = "sha256:9a3b0793b1bbfd4146304e23d90045f2a9b5fd5823aa682665fbdaf2a6c28f3e"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:f216dbce0e60e4d03e0c4353c7023b202d95cbaeff12e5fd2e82ea0a66905073"}, + {file = "pydantic_core-2.27.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a2e02889071850bbfd36b56fd6bc98945e23670773bc7a76657e90e6b6603c08"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42b0e23f119b2b456d07ca91b307ae167cc3f6c846a7b169fca5326e32fdc6cf"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:764be71193f87d460a03f1f7385a82e226639732214b402f9aa61f0d025f0737"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1c00666a3bd2f84920a4e94434f5974d7bbc57e461318d6bb34ce9cdbbc1f6b2"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ccaa88b24eebc0f849ce0a4d09e8a408ec5a94afff395eb69baf868f5183107"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c65af9088ac534313e1963443d0ec360bb2b9cba6c2909478d22c2e363d98a51"}, + {file = "pydantic_core-2.27.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206b5cf6f0c513baffaeae7bd817717140770c74528f3e4c3e1cec7871ddd61a"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:062f60e512fc7fff8b8a9d680ff0ddaaef0193dba9fa83e679c0c5f5fbd018bc"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:a0697803ed7d4af5e4c1adf1670af078f8fcab7a86350e969f454daf598c4960"}, + {file = "pydantic_core-2.27.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:58ca98a950171f3151c603aeea9303ef6c235f692fe555e883591103da709b23"}, + {file = "pydantic_core-2.27.1-cp313-none-win32.whl", hash = "sha256:8065914ff79f7eab1599bd80406681f0ad08f8e47c880f17b416c9f8f7a26d05"}, + {file = "pydantic_core-2.27.1-cp313-none-win_amd64.whl", hash = "sha256:ba630d5e3db74c79300d9a5bdaaf6200172b107f263c98a0539eeecb857b2337"}, + {file = "pydantic_core-2.27.1-cp313-none-win_arm64.whl", hash = "sha256:45cf8588c066860b623cd11c4ba687f8d7175d5f7ef65f7129df8a394c502de5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:5897bec80a09b4084aee23f9b73a9477a46c3304ad1d2d07acca19723fb1de62"}, + {file = "pydantic_core-2.27.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d0165ab2914379bd56908c02294ed8405c252250668ebcb438a55494c69f44ab"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b9af86e1d8e4cfc82c2022bfaa6f459381a50b94a29e95dcdda8442d6d83864"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5f6c8a66741c5f5447e047ab0ba7a1c61d1e95580d64bce852e3df1f895c4067"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9a42d6a8156ff78981f8aa56eb6394114e0dedb217cf8b729f438f643608cbcd"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64c65f40b4cd8b0e049a8edde07e38b476da7e3aaebe63287c899d2cff253fa5"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fdcf339322a3fae5cbd504edcefddd5a50d9ee00d968696846f089b4432cf78"}, + {file = "pydantic_core-2.27.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf99c8404f008750c846cb4ac4667b798a9f7de673ff719d705d9b2d6de49c5f"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8f1edcea27918d748c7e5e4d917297b2a0ab80cad10f86631e488b7cddf76a36"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_armv7l.whl", hash = "sha256:159cac0a3d096f79ab6a44d77a961917219707e2a130739c64d4dd46281f5c2a"}, + {file = "pydantic_core-2.27.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:029d9757eb621cc6e1848fa0b0310310de7301057f623985698ed7ebb014391b"}, + {file = "pydantic_core-2.27.1-cp38-none-win32.whl", hash = "sha256:a28af0695a45f7060e6f9b7092558a928a28553366519f64083c63a44f70e618"}, + {file = "pydantic_core-2.27.1-cp38-none-win_amd64.whl", hash = "sha256:2d4567c850905d5eaaed2f7a404e61012a51caf288292e016360aa2b96ff38d4"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:e9386266798d64eeb19dd3677051f5705bf873e98e15897ddb7d76f477131967"}, + {file = "pydantic_core-2.27.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4228b5b646caa73f119b1ae756216b59cc6e2267201c27d3912b592c5e323b60"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3dfe500de26c52abe0477dde16192ac39c98f05bf2d80e76102d394bd13854"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:aee66be87825cdf72ac64cb03ad4c15ffef4143dbf5c113f64a5ff4f81477bf9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b748c44bb9f53031c8cbc99a8a061bc181c1000c60a30f55393b6e9c45cc5bd"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ca038c7f6a0afd0b2448941b6ef9d5e1949e999f9e5517692eb6da58e9d44be"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e0bd57539da59a3e4671b90a502da9a28c72322a4f17866ba3ac63a82c4498e"}, + {file = "pydantic_core-2.27.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:ac6c2c45c847bbf8f91930d88716a0fb924b51e0c6dad329b793d670ec5db792"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b94d4ba43739bbe8b0ce4262bcc3b7b9f31459ad120fb595627eaeb7f9b9ca01"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_armv7l.whl", hash = "sha256:00e6424f4b26fe82d44577b4c842d7df97c20be6439e8e685d0d715feceb9fb9"}, + {file = "pydantic_core-2.27.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:38de0a70160dd97540335b7ad3a74571b24f1dc3ed33f815f0880682e6880131"}, + {file = "pydantic_core-2.27.1-cp39-none-win32.whl", hash = "sha256:7ccebf51efc61634f6c2344da73e366c75e735960b5654b63d7e6f69a5885fa3"}, + {file = "pydantic_core-2.27.1-cp39-none-win_amd64.whl", hash = "sha256:a57847b090d7892f123726202b7daa20df6694cbd583b67a592e856bff603d6c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:3fa80ac2bd5856580e242dbc202db873c60a01b20309c8319b5c5986fbe53ce6"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:d950caa237bb1954f1b8c9227b5065ba6875ac9771bb8ec790d956a699b78676"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e4216e64d203e39c62df627aa882f02a2438d18a5f21d7f721621f7a5d3611d"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:02a3d637bd387c41d46b002f0e49c52642281edacd2740e5a42f7017feea3f2c"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:161c27ccce13b6b0c8689418da3885d3220ed2eae2ea5e9b2f7f3d48f1d52c27"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:19910754e4cc9c63bc1c7f6d73aa1cfee82f42007e407c0f413695c2f7ed777f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e173486019cc283dc9778315fa29a363579372fe67045e971e89b6365cc035ed"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:af52d26579b308921b73b956153066481f064875140ccd1dfd4e77db89dbb12f"}, + {file = "pydantic_core-2.27.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:981fb88516bd1ae8b0cbbd2034678a39dedc98752f264ac9bc5839d3923fa04c"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5fde892e6c697ce3e30c61b239330fc5d569a71fefd4eb6512fc6caec9dd9e2f"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:816f5aa087094099fff7edabb5e01cc370eb21aa1a1d44fe2d2aefdfb5599b31"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c10c309e18e443ddb108f0ef64e8729363adbfd92d6d57beec680f6261556f3"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98476c98b02c8e9b2eec76ac4156fd006628b1b2d0ef27e548ffa978393fd154"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c3027001c28434e7ca5a6e1e527487051136aa81803ac812be51802150d880dd"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:7699b1df36a48169cdebda7ab5a2bac265204003f153b4bd17276153d997670a"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:1c39b07d90be6b48968ddc8c19e7585052088fd7ec8d568bb31ff64c70ae3c97"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:46ccfe3032b3915586e469d4972973f893c0a2bb65669194a5bdea9bacc088c2"}, + {file = "pydantic_core-2.27.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:62ba45e21cf6571d7f716d903b5b7b6d2617e2d5d67c0923dc47b9d41369f840"}, + {file = "pydantic_core-2.27.1.tar.gz", hash = "sha256:62a763352879b84aa31058fc931884055fd75089cccbd9d58bb6afd01141b235"}, ] [package.dependencies] @@ -1224,6 +1258,63 @@ dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pyte docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-asyncio" +version = "0.24.0" +description = "Pytest support for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_asyncio-0.24.0-py3-none-any.whl", hash = "sha256:a811296ed596b69bf0b6f3dc40f83bcaf341b155a269052d82efa2b25ac7037b"}, + {file = "pytest_asyncio-0.24.0.tar.gz", hash = "sha256:d081d828e576d85f875399194281e92bf8a68d60d72d1a2faf2feddb6c46b276"}, +] + +[package.dependencies] +pytest = ">=8.2,<9" + +[package.extras] +docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] +testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -1324,29 +1415,29 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.7.4" +version = "0.8.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, - {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, - {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, - {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, - {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, - {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, - {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, + {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"}, + {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"}, + {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"}, + {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"}, + {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"}, + {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"}, + {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"}, ] [[package]] @@ -1435,6 +1526,17 @@ files = [ [package.dependencies] httpx = {version = ">=0.26,<0.28", extras = ["http2"]} +[[package]] +name = "tomli" +version = "2.1.0" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomli-2.1.0-py3-none-any.whl", hash = "sha256:a5c57c3d1c56f5ccdf89f6523458f60ef716e210fc47c4cfb188c5ba473e0391"}, + {file = "tomli-2.1.0.tar.gz", hash = "sha256:3f646cae2aec94e17d04973e4249548320197cfabdf130015d023de4b74d8ab8"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -1750,4 +1852,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = ">=3.10,<4.0" -content-hash = "48184ad1281689c7743b8ca23135a647dc52257d54702d88b043fe31fe27ff27" +content-hash = "54bf6e076ec4d09be2307f07240018459dd6594efdc55a2dc2dc1d673184587e" diff --git a/autogpt_platform/autogpt_libs/pyproject.toml b/autogpt_platform/autogpt_libs/pyproject.toml index 62d29a94c9c7..e6d92933233d 100644 --- a/autogpt_platform/autogpt_libs/pyproject.toml +++ b/autogpt_platform/autogpt_libs/pyproject.toml @@ -10,16 +10,18 @@ packages = [{ include = "autogpt_libs" }] colorama = "^0.4.6" expiringdict = "^1.2.2" google-cloud-logging = "^3.11.3" -pydantic = "^2.9.2" +pydantic = "^2.10.2" pydantic-settings = "^2.6.1" pyjwt = "^2.10.0" +pytest-asyncio = "^0.24.0" +pytest-mock = "^3.14.0" python = ">=3.10,<4.0" python-dotenv = "^1.0.1" supabase = "^2.10.0" [tool.poetry.group.dev.dependencies] redis = "^5.2.0" -ruff = "^0.7.4" +ruff = "^0.8.0" [build-system] requires = ["poetry-core"] diff --git a/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py b/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py index f2441708ce31..ee94f016aa55 100644 --- a/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py +++ b/autogpt_platform/backend/backend/blocks/ai_image_generator_block.py @@ -2,12 +2,16 @@ from typing import Literal import replicate -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from replicate.helpers import FileOutput from backend.data.block import Block, BlockCategory, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) class ImageSize(str, Enum): diff --git a/autogpt_platform/backend/backend/blocks/ai_music_generator.py b/autogpt_platform/backend/backend/blocks/ai_music_generator.py index 6b2c086a0a80..f70d43ce370e 100644 --- a/autogpt_platform/backend/backend/blocks/ai_music_generator.py +++ b/autogpt_platform/backend/backend/blocks/ai_music_generator.py @@ -4,11 +4,15 @@ from typing import Literal import replicate -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) logger = logging.getLogger(__name__) diff --git a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py index e70f8fd40e7d..08023b877118 100644 --- a/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py +++ b/autogpt_platform/backend/backend/blocks/ai_shortform_video_block.py @@ -3,11 +3,15 @@ from enum import Enum from typing import Literal -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) from backend.util.request import requests TEST_CREDENTIALS = APIKeyCredentials( diff --git a/autogpt_platform/backend/backend/blocks/branching.py b/autogpt_platform/backend/backend/blocks/branching.py index 65a01c977268..daf967bc6a7d 100644 --- a/autogpt_platform/backend/backend/blocks/branching.py +++ b/autogpt_platform/backend/backend/blocks/branching.py @@ -75,11 +75,17 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: value1 = input_data.value1 if isinstance(value1, str): - value1 = float(value1.strip()) + try: + value1 = float(value1.strip()) + except ValueError: + value1 = value1.strip() value2 = input_data.value2 if isinstance(value2, str): - value2 = float(value2.strip()) + try: + value2 = float(value2.strip()) + except ValueError: + value2 = value2.strip() yes_value = input_data.yes_value if input_data.yes_value is not None else value1 no_value = input_data.no_value if input_data.no_value is not None else value2 diff --git a/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py b/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py new file mode 100644 index 000000000000..13f9e3977941 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/count_words_and_char_block.py @@ -0,0 +1,43 @@ +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField + + +class WordCharacterCountBlock(Block): + class Input(BlockSchema): + text: str = SchemaField( + description="Input text to count words and characters", + placeholder="Enter your text here", + advanced=False, + ) + + class Output(BlockSchema): + word_count: int = SchemaField(description="Number of words in the input text") + character_count: int = SchemaField( + description="Number of characters in the input text" + ) + error: str = SchemaField( + description="Error message if the counting operation failed" + ) + + def __init__(self): + super().__init__( + id="ab2a782d-22cf-4587-8a70-55b59b3f9f90", + description="Counts the number of words and characters in a given text.", + categories={BlockCategory.TEXT}, + input_schema=WordCharacterCountBlock.Input, + output_schema=WordCharacterCountBlock.Output, + test_input={"text": "Hello, how are you?"}, + test_output=[("word_count", 4), ("character_count", 19)], + ) + + def run(self, input_data: Input, **kwargs) -> BlockOutput: + try: + text = input_data.text + word_count = len(text.split()) + character_count = len(text) + + yield "word_count", word_count + yield "character_count", character_count + + except Exception as e: + yield "error", str(e) diff --git a/autogpt_platform/backend/backend/blocks/discord.py b/autogpt_platform/backend/backend/blocks/discord.py index 69d3c3bc744c..c638e402508b 100644 --- a/autogpt_platform/backend/backend/blocks/discord.py +++ b/autogpt_platform/backend/backend/blocks/discord.py @@ -3,11 +3,15 @@ import aiohttp import discord -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) DiscordCredentials = CredentialsMetaInput[Literal["discord"], Literal["api_key"]] diff --git a/autogpt_platform/backend/backend/blocks/fal/_auth.py b/autogpt_platform/backend/backend/blocks/fal/_auth.py new file mode 100644 index 000000000000..3271ee095028 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/fal/_auth.py @@ -0,0 +1,36 @@ +from typing import Literal + +from pydantic import SecretStr + +from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput + +FalCredentials = APIKeyCredentials +FalCredentialsInput = CredentialsMetaInput[ + Literal["fal"], + Literal["api_key"], +] + +TEST_CREDENTIALS = APIKeyCredentials( + id="01234567-89ab-cdef-0123-456789abcdef", + provider="fal", + api_key=SecretStr("mock-fal-api-key"), + title="Mock FAL API key", + expires_at=None, +) +TEST_CREDENTIALS_INPUT = { + "provider": TEST_CREDENTIALS.provider, + "id": TEST_CREDENTIALS.id, + "type": TEST_CREDENTIALS.type, + "title": TEST_CREDENTIALS.title, +} + + +def FalCredentialsField() -> FalCredentialsInput: + """ + Creates a FAL credentials input on a block. + """ + return CredentialsField( + provider="fal", + supported_credential_types={"api_key"}, + description="The FAL integration can be used with an API Key.", + ) diff --git a/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py b/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py new file mode 100644 index 000000000000..dbe8d7220a40 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/fal/ai_video_generator.py @@ -0,0 +1,199 @@ +import logging +import time +from enum import Enum +from typing import Any, Dict + +import httpx + +from backend.blocks.fal._auth import ( + TEST_CREDENTIALS, + TEST_CREDENTIALS_INPUT, + FalCredentials, + FalCredentialsField, + FalCredentialsInput, +) +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField + +logger = logging.getLogger(__name__) + + +class FalModel(str, Enum): + MOCHI = "fal-ai/mochi-v1" + LUMA = "fal-ai/luma-dream-machine" + + +class AIVideoGeneratorBlock(Block): + class Input(BlockSchema): + prompt: str = SchemaField( + description="Description of the video to generate.", + placeholder="A dog running in a field.", + ) + model: FalModel = SchemaField( + title="FAL Model", + default=FalModel.MOCHI, + description="The FAL model to use for video generation.", + ) + credentials: FalCredentialsInput = FalCredentialsField() + + class Output(BlockSchema): + video_url: str = SchemaField(description="The URL of the generated video.") + error: str = SchemaField( + description="Error message if video generation failed." + ) + logs: list[str] = SchemaField( + description="Generation progress logs.", optional=True + ) + + def __init__(self): + super().__init__( + id="530cf046-2ce0-4854-ae2c-659db17c7a46", + description="Generate videos using FAL AI models.", + categories={BlockCategory.AI}, + input_schema=self.Input, + output_schema=self.Output, + test_input={ + "prompt": "A dog running in a field.", + "model": FalModel.MOCHI, + "credentials": TEST_CREDENTIALS_INPUT, + }, + test_credentials=TEST_CREDENTIALS, + test_output=[("video_url", "https://fal.media/files/example/video.mp4")], + test_mock={ + "generate_video": lambda *args, **kwargs: "https://fal.media/files/example/video.mp4" + }, + ) + + def _get_headers(self, api_key: str) -> Dict[str, str]: + """Get headers for FAL API requests.""" + return { + "Authorization": f"Key {api_key}", + "Content-Type": "application/json", + } + + def _submit_request( + self, url: str, headers: Dict[str, str], data: Dict[str, Any] + ) -> Dict[str, Any]: + """Submit a request to the FAL API.""" + try: + response = httpx.post(url, headers=headers, json=data) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + logger.error(f"FAL API request failed: {str(e)}") + raise RuntimeError(f"Failed to submit request: {str(e)}") + + def _poll_status(self, status_url: str, headers: Dict[str, str]) -> Dict[str, Any]: + """Poll the status endpoint until completion or failure.""" + try: + response = httpx.get(status_url, headers=headers) + response.raise_for_status() + return response.json() + except httpx.HTTPError as e: + logger.error(f"Failed to get status: {str(e)}") + raise RuntimeError(f"Failed to get status: {str(e)}") + + def generate_video(self, input_data: Input, credentials: FalCredentials) -> str: + """Generate video using the specified FAL model.""" + base_url = "https://queue.fal.run" + api_key = credentials.api_key.get_secret_value() + headers = self._get_headers(api_key) + + # Submit generation request + submit_url = f"{base_url}/{input_data.model.value}" + submit_data = {"prompt": input_data.prompt} + + seen_logs = set() + + try: + # Submit request to queue + submit_response = httpx.post(submit_url, headers=headers, json=submit_data) + submit_response.raise_for_status() + request_data = submit_response.json() + + # Get request_id and urls from initial response + request_id = request_data.get("request_id") + status_url = request_data.get("status_url") + result_url = request_data.get("response_url") + + if not all([request_id, status_url, result_url]): + raise ValueError("Missing required data in submission response") + + # Poll for status with exponential backoff + max_attempts = 30 + attempt = 0 + base_wait_time = 5 + + while attempt < max_attempts: + status_response = httpx.get(f"{status_url}?logs=1", headers=headers) + status_response.raise_for_status() + status_data = status_response.json() + + # Process new logs only + logs = status_data.get("logs", []) + if logs and isinstance(logs, list): + for log in logs: + if isinstance(log, dict): + # Create a unique key for this log entry + log_key = ( + f"{log.get('timestamp', '')}-{log.get('message', '')}" + ) + if log_key not in seen_logs: + seen_logs.add(log_key) + message = log.get("message", "") + if message: + logger.debug( + f"[FAL Generation] [{log.get('level', 'INFO')}] [{log.get('source', '')}] [{log.get('timestamp', '')}] {message}" + ) + + status = status_data.get("status") + if status == "COMPLETED": + # Get the final result + result_response = httpx.get(result_url, headers=headers) + result_response.raise_for_status() + result_data = result_response.json() + + if "video" not in result_data or not isinstance( + result_data["video"], dict + ): + raise ValueError("Invalid response format - missing video data") + + video_url = result_data["video"].get("url") + if not video_url: + raise ValueError("No video URL in response") + + return video_url + + elif status == "FAILED": + error_msg = status_data.get("error", "No error details provided") + raise RuntimeError(f"Video generation failed: {error_msg}") + elif status == "IN_QUEUE": + position = status_data.get("queue_position", "unknown") + logger.debug( + f"[FAL Generation] Status: In queue, position: {position}" + ) + elif status == "IN_PROGRESS": + logger.debug( + "[FAL Generation] Status: Request is being processed..." + ) + else: + logger.info(f"[FAL Generation] Status: Unknown status: {status}") + + wait_time = min(base_wait_time * (2**attempt), 60) # Cap at 60 seconds + time.sleep(wait_time) + attempt += 1 + + raise RuntimeError("Maximum polling attempts reached") + + except httpx.HTTPError as e: + raise RuntimeError(f"API request failed: {str(e)}") + + def run( + self, input_data: Input, *, credentials: FalCredentials, **kwargs + ) -> BlockOutput: + try: + video_url = self.generate_video(input_data, credentials) + yield "video_url", video_url + except Exception as e: + error_message = str(e) + yield "error", error_message diff --git a/autogpt_platform/backend/backend/blocks/github/_api.py b/autogpt_platform/backend/backend/blocks/github/_api.py index 1965caa76448..6ec91eeb37db 100644 --- a/autogpt_platform/backend/backend/blocks/github/_api.py +++ b/autogpt_platform/backend/backend/blocks/github/_api.py @@ -35,9 +35,9 @@ def _get_headers(credentials: GithubCredentials) -> dict[str, str]: } -def get_api(credentials: GithubCredentials) -> Requests: +def get_api(credentials: GithubCredentials, convert_urls: bool = True) -> Requests: return Requests( trusted_origins=["https://api.github.com", "https://github.com"], - extra_url_validator=_convert_to_api_url, + extra_url_validator=_convert_to_api_url if convert_urls else None, extra_headers=_get_headers(credentials), ) diff --git a/autogpt_platform/backend/backend/blocks/github/_auth.py b/autogpt_platform/backend/backend/blocks/github/_auth.py index 4ea31e98be9f..72aa8f648015 100644 --- a/autogpt_platform/backend/backend/blocks/github/_auth.py +++ b/autogpt_platform/backend/backend/blocks/github/_auth.py @@ -1,12 +1,13 @@ from typing import Literal -from autogpt_libs.supabase_integration_credentials_store.types import ( +from pydantic import SecretStr + +from backend.data.model import ( APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, OAuth2Credentials, ) -from pydantic import SecretStr - -from backend.data.model import CredentialsField, CredentialsMetaInput from backend.util.settings import Secrets secrets = Secrets() diff --git a/autogpt_platform/backend/backend/blocks/github/issues.py b/autogpt_platform/backend/backend/blocks/github/issues.py index 24790accec9e..ff1d0a41ad0e 100644 --- a/autogpt_platform/backend/backend/blocks/github/issues.py +++ b/autogpt_platform/backend/backend/blocks/github/issues.py @@ -46,15 +46,27 @@ def __init__(self): categories={BlockCategory.DEVELOPER_TOOLS}, input_schema=GithubCommentBlock.Input, output_schema=GithubCommentBlock.Output, - test_input={ - "issue_url": "https://github.com/owner/repo/issues/1", - "comment": "This is a test comment.", - "credentials": TEST_CREDENTIALS_INPUT, - }, + test_input=[ + { + "issue_url": "https://github.com/owner/repo/issues/1", + "comment": "This is a test comment.", + "credentials": TEST_CREDENTIALS_INPUT, + }, + { + "issue_url": "https://github.com/owner/repo/pull/1", + "comment": "This is a test comment.", + "credentials": TEST_CREDENTIALS_INPUT, + }, + ], test_credentials=TEST_CREDENTIALS, test_output=[ ("id", 1337), ("url", "https://github.com/owner/repo/issues/1#issuecomment-1337"), + ("id", 1337), + ( + "url", + "https://github.com/owner/repo/issues/1#issuecomment-1337", + ), ], test_mock={ "post_comment": lambda *args, **kwargs: ( @@ -70,6 +82,8 @@ def post_comment( ) -> tuple[int, str]: api = get_api(credentials) data = {"body": body_text} + if "pull" in issue_url: + issue_url = issue_url.replace("pull", "issues") comments_url = issue_url + "/comments" response = api.post(comments_url, json=data) comment = response.json() @@ -234,9 +248,12 @@ def run( credentials, input_data.issue_url, ) - yield "title", title - yield "body", body - yield "user", user + if title: + yield "title", title + if body: + yield "body", body + if user: + yield "user", user class GithubListIssuesBlock(Block): diff --git a/autogpt_platform/backend/backend/blocks/google/_auth.py b/autogpt_platform/backend/backend/blocks/google/_auth.py index 742fcb36fa17..ccae2e462244 100644 --- a/autogpt_platform/backend/backend/blocks/google/_auth.py +++ b/autogpt_platform/backend/backend/blocks/google/_auth.py @@ -1,9 +1,8 @@ from typing import Literal -from autogpt_libs.supabase_integration_credentials_store.types import OAuth2Credentials from pydantic import SecretStr -from backend.data.model import CredentialsField, CredentialsMetaInput +from backend.data.model import CredentialsField, CredentialsMetaInput, OAuth2Credentials from backend.util.settings import Secrets # --8<-- [start:GoogleOAuthIsConfigured] diff --git a/autogpt_platform/backend/backend/blocks/google_maps.py b/autogpt_platform/backend/backend/blocks/google_maps.py index 97d91c83705e..d211fe8ff3f0 100644 --- a/autogpt_platform/backend/backend/blocks/google_maps.py +++ b/autogpt_platform/backend/backend/blocks/google_maps.py @@ -1,11 +1,15 @@ from typing import Literal import googlemaps -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import BaseModel, SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", diff --git a/autogpt_platform/backend/backend/blocks/hubspot/_auth.py b/autogpt_platform/backend/backend/blocks/hubspot/_auth.py new file mode 100644 index 000000000000..c32af8c38c01 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/hubspot/_auth.py @@ -0,0 +1,36 @@ +from typing import Literal + +from pydantic import SecretStr + +from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput + +HubSpotCredentials = APIKeyCredentials +HubSpotCredentialsInput = CredentialsMetaInput[ + Literal["hubspot"], + Literal["api_key"], +] + + +def HubSpotCredentialsField() -> HubSpotCredentialsInput: + """Creates a HubSpot credentials input on a block.""" + return CredentialsField( + provider="hubspot", + supported_credential_types={"api_key"}, + description="The HubSpot integration requires an API Key.", + ) + + +TEST_CREDENTIALS = APIKeyCredentials( + id="01234567-89ab-cdef-0123-456789abcdef", + provider="hubspot", + api_key=SecretStr("mock-hubspot-api-key"), + title="Mock HubSpot API key", + expires_at=None, +) + +TEST_CREDENTIALS_INPUT = { + "provider": TEST_CREDENTIALS.provider, + "id": TEST_CREDENTIALS.id, + "type": TEST_CREDENTIALS.type, + "title": TEST_CREDENTIALS.title, +} diff --git a/autogpt_platform/backend/backend/blocks/hubspot/company.py b/autogpt_platform/backend/backend/blocks/hubspot/company.py new file mode 100644 index 000000000000..3e9406103f57 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/hubspot/company.py @@ -0,0 +1,106 @@ +from backend.blocks.hubspot._auth import ( + HubSpotCredentials, + HubSpotCredentialsField, + HubSpotCredentialsInput, +) +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField +from backend.util.request import requests + + +class HubSpotCompanyBlock(Block): + class Input(BlockSchema): + credentials: HubSpotCredentialsInput = HubSpotCredentialsField() + operation: str = SchemaField( + description="Operation to perform (create, update, get)", default="get" + ) + company_data: dict = SchemaField( + description="Company data for create/update operations", default={} + ) + domain: str = SchemaField( + description="Company domain for get/update operations", default="" + ) + + class Output(BlockSchema): + company: dict = SchemaField(description="Company information") + status: str = SchemaField(description="Operation status") + + def __init__(self): + super().__init__( + id="3ae02219-d540-47cd-9c78-3ad6c7d9820a", + description="Manages HubSpot companies - create, update, and retrieve company information", + categories={BlockCategory.CRM}, + input_schema=HubSpotCompanyBlock.Input, + output_schema=HubSpotCompanyBlock.Output, + ) + + def run( + self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs + ) -> BlockOutput: + base_url = "https://api.hubapi.com/crm/v3/objects/companies" + headers = { + "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", + "Content-Type": "application/json", + } + + if input_data.operation == "create": + response = requests.post( + base_url, headers=headers, json={"properties": input_data.company_data} + ) + result = response.json() + yield "company", result + yield "status", "created" + + elif input_data.operation == "get": + search_url = f"{base_url}/search" + search_data = { + "filterGroups": [ + { + "filters": [ + { + "propertyName": "domain", + "operator": "EQ", + "value": input_data.domain, + } + ] + } + ] + } + response = requests.post(search_url, headers=headers, json=search_data) + result = response.json() + yield "company", result.get("results", [{}])[0] + yield "status", "retrieved" + + elif input_data.operation == "update": + # First get company ID by domain + search_response = requests.post( + f"{base_url}/search", + headers=headers, + json={ + "filterGroups": [ + { + "filters": [ + { + "propertyName": "domain", + "operator": "EQ", + "value": input_data.domain, + } + ] + } + ] + }, + ) + company_id = search_response.json().get("results", [{}])[0].get("id") + + if company_id: + response = requests.patch( + f"{base_url}/{company_id}", + headers=headers, + json={"properties": input_data.company_data}, + ) + result = response.json() + yield "company", result + yield "status", "updated" + else: + yield "company", {} + yield "status", "company_not_found" diff --git a/autogpt_platform/backend/backend/blocks/hubspot/contact.py b/autogpt_platform/backend/backend/blocks/hubspot/contact.py new file mode 100644 index 000000000000..e4a01cbb3bd4 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/hubspot/contact.py @@ -0,0 +1,106 @@ +from backend.blocks.hubspot._auth import ( + HubSpotCredentials, + HubSpotCredentialsField, + HubSpotCredentialsInput, +) +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField +from backend.util.request import requests + + +class HubSpotContactBlock(Block): + class Input(BlockSchema): + credentials: HubSpotCredentialsInput = HubSpotCredentialsField() + operation: str = SchemaField( + description="Operation to perform (create, update, get)", default="get" + ) + contact_data: dict = SchemaField( + description="Contact data for create/update operations", default={} + ) + email: str = SchemaField( + description="Email address for get/update operations", default="" + ) + + class Output(BlockSchema): + contact: dict = SchemaField(description="Contact information") + status: str = SchemaField(description="Operation status") + + def __init__(self): + super().__init__( + id="5267326e-c4c1-4016-9f54-4e72ad02f813", + description="Manages HubSpot contacts - create, update, and retrieve contact information", + categories={BlockCategory.CRM}, + input_schema=HubSpotContactBlock.Input, + output_schema=HubSpotContactBlock.Output, + ) + + def run( + self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs + ) -> BlockOutput: + base_url = "https://api.hubapi.com/crm/v3/objects/contacts" + headers = { + "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", + "Content-Type": "application/json", + } + + if input_data.operation == "create": + response = requests.post( + base_url, headers=headers, json={"properties": input_data.contact_data} + ) + result = response.json() + yield "contact", result + yield "status", "created" + + elif input_data.operation == "get": + # Search for contact by email + search_url = f"{base_url}/search" + search_data = { + "filterGroups": [ + { + "filters": [ + { + "propertyName": "email", + "operator": "EQ", + "value": input_data.email, + } + ] + } + ] + } + response = requests.post(search_url, headers=headers, json=search_data) + result = response.json() + yield "contact", result.get("results", [{}])[0] + yield "status", "retrieved" + + elif input_data.operation == "update": + search_response = requests.post( + f"{base_url}/search", + headers=headers, + json={ + "filterGroups": [ + { + "filters": [ + { + "propertyName": "email", + "operator": "EQ", + "value": input_data.email, + } + ] + } + ] + }, + ) + contact_id = search_response.json().get("results", [{}])[0].get("id") + + if contact_id: + response = requests.patch( + f"{base_url}/{contact_id}", + headers=headers, + json={"properties": input_data.contact_data}, + ) + result = response.json() + yield "contact", result + yield "status", "updated" + else: + yield "contact", {} + yield "status", "contact_not_found" diff --git a/autogpt_platform/backend/backend/blocks/hubspot/engagement.py b/autogpt_platform/backend/backend/blocks/hubspot/engagement.py new file mode 100644 index 000000000000..427cf051d599 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/hubspot/engagement.py @@ -0,0 +1,121 @@ +from datetime import datetime, timedelta + +from backend.blocks.hubspot._auth import ( + HubSpotCredentials, + HubSpotCredentialsField, + HubSpotCredentialsInput, +) +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField +from backend.util.request import requests + + +class HubSpotEngagementBlock(Block): + class Input(BlockSchema): + credentials: HubSpotCredentialsInput = HubSpotCredentialsField() + operation: str = SchemaField( + description="Operation to perform (send_email, track_engagement)", + default="send_email", + ) + email_data: dict = SchemaField( + description="Email data including recipient, subject, content", + default={}, + ) + contact_id: str = SchemaField( + description="Contact ID for engagement tracking", default="" + ) + timeframe_days: int = SchemaField( + description="Number of days to look back for engagement", + default=30, + optional=True, + ) + + class Output(BlockSchema): + result: dict = SchemaField(description="Operation result") + status: str = SchemaField(description="Operation status") + + def __init__(self): + super().__init__( + id="c6524385-7d87-49d6-a470-248bd29ca765", + description="Manages HubSpot engagements - sends emails and tracks engagement metrics", + categories={BlockCategory.CRM, BlockCategory.COMMUNICATION}, + input_schema=HubSpotEngagementBlock.Input, + output_schema=HubSpotEngagementBlock.Output, + ) + + def run( + self, input_data: Input, *, credentials: HubSpotCredentials, **kwargs + ) -> BlockOutput: + base_url = "https://api.hubapi.com" + headers = { + "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", + "Content-Type": "application/json", + } + + if input_data.operation == "send_email": + # Using the email send API + email_url = f"{base_url}/crm/v3/objects/emails" + email_data = { + "properties": { + "hs_timestamp": datetime.now().isoformat(), + "hubspot_owner_id": "1", # This should be configurable + "hs_email_direction": "OUTBOUND", + "hs_email_status": "SEND", + "hs_email_subject": input_data.email_data.get("subject"), + "hs_email_text": input_data.email_data.get("content"), + "hs_email_to_email": input_data.email_data.get("recipient"), + } + } + + response = requests.post(email_url, headers=headers, json=email_data) + result = response.json() + yield "result", result + yield "status", "email_sent" + + elif input_data.operation == "track_engagement": + # Get engagement events for the contact + from_date = datetime.now() - timedelta(days=input_data.timeframe_days) + engagement_url = ( + f"{base_url}/crm/v3/objects/contacts/{input_data.contact_id}/engagement" + ) + + params = {"limit": 100, "after": from_date.isoformat()} + + response = requests.get(engagement_url, headers=headers, params=params) + engagements = response.json() + + # Process engagement metrics + metrics = { + "email_opens": 0, + "email_clicks": 0, + "email_replies": 0, + "last_engagement": None, + "engagement_score": 0, + } + + for engagement in engagements.get("results", []): + eng_type = engagement.get("properties", {}).get("hs_engagement_type") + if eng_type == "EMAIL": + metrics["email_opens"] += 1 + elif eng_type == "EMAIL_CLICK": + metrics["email_clicks"] += 1 + elif eng_type == "EMAIL_REPLY": + metrics["email_replies"] += 1 + + # Update last engagement time + eng_time = engagement.get("properties", {}).get("hs_timestamp") + if eng_time and ( + not metrics["last_engagement"] + or eng_time > metrics["last_engagement"] + ): + metrics["last_engagement"] = eng_time + + # Calculate simple engagement score + metrics["engagement_score"] = ( + metrics["email_opens"] + + metrics["email_clicks"] * 2 + + metrics["email_replies"] * 3 + ) + + yield "result", metrics + yield "status", "engagement_tracked" diff --git a/autogpt_platform/backend/backend/blocks/ideogram.py b/autogpt_platform/backend/backend/blocks/ideogram.py index 8c9e4b6f5933..b6a21e7acec2 100644 --- a/autogpt_platform/backend/backend/blocks/ideogram.py +++ b/autogpt_platform/backend/backend/blocks/ideogram.py @@ -1,12 +1,16 @@ from enum import Enum from typing import Any, Dict, Literal, Optional -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from requests.exceptions import RequestException from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) from backend.util.request import requests TEST_CREDENTIALS = APIKeyCredentials( diff --git a/autogpt_platform/backend/backend/blocks/jina/_auth.py b/autogpt_platform/backend/backend/blocks/jina/_auth.py index c8d9f6b05a77..2bffeecce754 100644 --- a/autogpt_platform/backend/backend/blocks/jina/_auth.py +++ b/autogpt_platform/backend/backend/blocks/jina/_auth.py @@ -1,9 +1,8 @@ from typing import Literal -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr -from backend.data.model import CredentialsField, CredentialsMetaInput +from backend.data.model import APIKeyCredentials, CredentialsField, CredentialsMetaInput JinaCredentials = APIKeyCredentials JinaCredentialsInput = CredentialsMetaInput[ diff --git a/autogpt_platform/backend/backend/blocks/jina/fact_checker.py b/autogpt_platform/backend/backend/blocks/jina/fact_checker.py new file mode 100644 index 000000000000..c9b8c08d1db8 --- /dev/null +++ b/autogpt_platform/backend/backend/blocks/jina/fact_checker.py @@ -0,0 +1,59 @@ +from urllib.parse import quote + +import requests + +from backend.blocks.jina._auth import ( + JinaCredentials, + JinaCredentialsField, + JinaCredentialsInput, +) +from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema +from backend.data.model import SchemaField + + +class FactCheckerBlock(Block): + class Input(BlockSchema): + statement: str = SchemaField( + description="The statement to check for factuality" + ) + credentials: JinaCredentialsInput = JinaCredentialsField() + + class Output(BlockSchema): + factuality: float = SchemaField( + description="The factuality score of the statement" + ) + result: bool = SchemaField(description="The result of the factuality check") + reason: str = SchemaField(description="The reason for the factuality result") + error: str = SchemaField(description="Error message if the check fails") + + def __init__(self): + super().__init__( + id="d38b6c5e-9968-4271-8423-6cfe60d6e7e6", + description="This block checks the factuality of a given statement using Jina AI's Grounding API.", + categories={BlockCategory.SEARCH}, + input_schema=FactCheckerBlock.Input, + output_schema=FactCheckerBlock.Output, + ) + + def run( + self, input_data: Input, *, credentials: JinaCredentials, **kwargs + ) -> BlockOutput: + encoded_statement = quote(input_data.statement) + url = f"https://g.jina.ai/{encoded_statement}" + + headers = { + "Accept": "application/json", + "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", + } + + response = requests.get(url, headers=headers) + response.raise_for_status() + data = response.json() + + if "data" in data: + data = data["data"] + yield "factuality", data["factuality"] + yield "result", data["result"] + yield "reason", data["reason"] + else: + raise RuntimeError(f"Expected 'data' key not found in response: {data}") diff --git a/autogpt_platform/backend/backend/blocks/jina/search.py b/autogpt_platform/backend/backend/blocks/jina/search.py index 5d13fb5d690c..c03ca3ce0134 100644 --- a/autogpt_platform/backend/backend/blocks/jina/search.py +++ b/autogpt_platform/backend/backend/blocks/jina/search.py @@ -55,3 +55,53 @@ def run( # Output the search results yield "results", results + + +class ExtractWebsiteContentBlock(Block, GetRequest): + class Input(BlockSchema): + credentials: JinaCredentialsInput = JinaCredentialsField() + url: str = SchemaField(description="The URL to scrape the content from") + raw_content: bool = SchemaField( + default=False, + title="Raw Content", + description="Whether to do a raw scrape of the content or use Jina-ai Reader to scrape the content", + advanced=True, + ) + + class Output(BlockSchema): + content: str = SchemaField(description="The scraped content from the given URL") + error: str = SchemaField( + description="Error message if the content cannot be retrieved" + ) + + def __init__(self): + super().__init__( + id="436c3984-57fd-4b85-8e9a-459b356883bd", + description="This block scrapes the content from the given web URL.", + categories={BlockCategory.SEARCH}, + input_schema=ExtractWebsiteContentBlock.Input, + output_schema=ExtractWebsiteContentBlock.Output, + test_input={ + "url": "https://en.wikipedia.org/wiki/Artificial_intelligence", + "credentials": TEST_CREDENTIALS_INPUT, + }, + test_credentials=TEST_CREDENTIALS, + test_output=("content", "scraped content"), + test_mock={"get_request": lambda *args, **kwargs: "scraped content"}, + ) + + def run( + self, input_data: Input, *, credentials: JinaCredentials, **kwargs + ) -> BlockOutput: + if input_data.raw_content: + url = input_data.url + headers = {} + else: + url = f"https://r.jina.ai/{input_data.url}" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {credentials.api_key.get_secret_value()}", + } + + content = self.get_request(url, json=False, headers=headers) + yield "content", content diff --git a/autogpt_platform/backend/backend/blocks/llm.py b/autogpt_platform/backend/backend/blocks/llm.py index 71e1f2c3a365..50a9bfb3c626 100644 --- a/autogpt_platform/backend/backend/blocks/llm.py +++ b/autogpt_platform/backend/backend/blocks/llm.py @@ -5,7 +5,6 @@ from types import MappingProxyType from typing import TYPE_CHECKING, Any, List, Literal, NamedTuple -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr if TYPE_CHECKING: @@ -17,19 +16,17 @@ from groq import Groq from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) from backend.util import json from backend.util.settings import BehaveAs, Settings logger = logging.getLogger(__name__) -# LlmApiKeys = { -# "openai": BlockSecret("openai_api_key"), -# "anthropic": BlockSecret("anthropic_api_key"), -# "groq": BlockSecret("groq_api_key"), -# "ollama": BlockSecret(value=""), -# } - LLMProviderName = Literal["anthropic", "groq", "openai", "ollama", "open_router"] AICredentials = CredentialsMetaInput[LLMProviderName, Literal["api_key"]] @@ -526,7 +523,7 @@ def parse_response(resp: str) -> tuple[dict[str, Any], str | None]: class AITextGeneratorBlock(Block): class Input(BlockSchema): prompt: str = SchemaField( - description="The prompt to send to the language model.", + description="The prompt to send to the language model. You can use any of the {keys} from Prompt Values to fill in the prompt with values from the prompt values dictionary by putting them in curly braces.", placeholder="Enter your prompt here...", ) model: LlmModel = SchemaField( diff --git a/autogpt_platform/backend/backend/blocks/medium.py b/autogpt_platform/backend/backend/blocks/medium.py index 60abb4659808..da8c367c7860 100644 --- a/autogpt_platform/backend/backend/blocks/medium.py +++ b/autogpt_platform/backend/backend/blocks/medium.py @@ -1,11 +1,11 @@ from enum import Enum from typing import List, Literal -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema from backend.data.model import ( + APIKeyCredentials, BlockSecret, CredentialsField, CredentialsMetaInput, diff --git a/autogpt_platform/backend/backend/blocks/pinecone.py b/autogpt_platform/backend/backend/blocks/pinecone.py index 5ef8e639920f..a62c1fa77739 100644 --- a/autogpt_platform/backend/backend/blocks/pinecone.py +++ b/autogpt_platform/backend/backend/blocks/pinecone.py @@ -1,11 +1,15 @@ import uuid from typing import Any, Literal -from autogpt_libs.supabase_integration_credentials_store import APIKeyCredentials from pinecone import Pinecone, ServerlessSpec from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) PineconeCredentials = APIKeyCredentials PineconeCredentialsInput = CredentialsMetaInput[ diff --git a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py index 9200084c470d..b346c87a38a0 100644 --- a/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py +++ b/autogpt_platform/backend/backend/blocks/replicate_flux_advanced.py @@ -3,12 +3,16 @@ from typing import Literal import replicate -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from replicate.helpers import FileOutput from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", diff --git a/autogpt_platform/backend/backend/blocks/search.py b/autogpt_platform/backend/backend/blocks/search.py index dffe8b97c872..f89232703aa6 100644 --- a/autogpt_platform/backend/backend/blocks/search.py +++ b/autogpt_platform/backend/backend/blocks/search.py @@ -1,12 +1,16 @@ from typing import Literal from urllib.parse import quote -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.blocks.helpers.http import GetRequest from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) class GetWikipediaSummaryBlock(Block, GetRequest): @@ -40,44 +44,6 @@ def run(self, input_data: Input, **kwargs) -> BlockOutput: yield "summary", response["extract"] -class ExtractWebsiteContentBlock(Block, GetRequest): - class Input(BlockSchema): - url: str = SchemaField(description="The URL to scrape the content from") - raw_content: bool = SchemaField( - default=False, - title="Raw Content", - description="Whether to do a raw scrape of the content or use Jina-ai Reader to scrape the content", - advanced=True, - ) - - class Output(BlockSchema): - content: str = SchemaField(description="The scraped content from the given URL") - error: str = SchemaField( - description="Error message if the content cannot be retrieved" - ) - - def __init__(self): - super().__init__( - id="436c3984-57fd-4b85-8e9a-459b356883bd", - description="This block scrapes the content from the given web URL.", - categories={BlockCategory.SEARCH}, - input_schema=ExtractWebsiteContentBlock.Input, - output_schema=ExtractWebsiteContentBlock.Output, - test_input={"url": "https://en.wikipedia.org/wiki/Artificial_intelligence"}, - test_output=("content", "scraped content"), - test_mock={"get_request": lambda url, json: "scraped content"}, - ) - - def run(self, input_data: Input, **kwargs) -> BlockOutput: - if input_data.raw_content: - url = input_data.url - else: - url = f"https://r.jina.ai/{input_data.url}" - - content = self.get_request(url, json=False) - yield "content", content - - TEST_CREDENTIALS = APIKeyCredentials( id="01234567-89ab-cdef-0123-456789abcdef", provider="openweathermap", diff --git a/autogpt_platform/backend/backend/blocks/talking_head.py b/autogpt_platform/backend/backend/blocks/talking_head.py index 9f372b0abf9d..20aadcd214fe 100644 --- a/autogpt_platform/backend/backend/blocks/talking_head.py +++ b/autogpt_platform/backend/backend/blocks/talking_head.py @@ -1,11 +1,15 @@ import time from typing import Literal -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) from backend.util.request import requests TEST_CREDENTIALS = APIKeyCredentials( diff --git a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py index 510d02817651..b92cc9fa4468 100644 --- a/autogpt_platform/backend/backend/blocks/text_to_speech_block.py +++ b/autogpt_platform/backend/backend/blocks/text_to_speech_block.py @@ -1,10 +1,14 @@ from typing import Any, Literal -from autogpt_libs.supabase_integration_credentials_store.types import APIKeyCredentials from pydantic import SecretStr from backend.data.block import Block, BlockCategory, BlockOutput, BlockSchema -from backend.data.model import CredentialsField, CredentialsMetaInput, SchemaField +from backend.data.model import ( + APIKeyCredentials, + CredentialsField, + CredentialsMetaInput, + SchemaField, +) from backend.util.request import requests TEST_CREDENTIALS = APIKeyCredentials( diff --git a/autogpt_platform/backend/backend/cli.py b/autogpt_platform/backend/backend/cli.py index 154c22207bb6..efaadd02b282 100755 --- a/autogpt_platform/backend/backend/cli.py +++ b/autogpt_platform/backend/backend/cli.py @@ -93,6 +93,16 @@ def stop(): print("Server Stopped") +@main.command() +def gen_encrypt_key(): + """ + Generate a new encryption key + """ + from cryptography.fernet import Fernet + + print(Fernet.generate_key().decode()) + + @click.group() def test(): """ diff --git a/autogpt_platform/backend/backend/data/block.py b/autogpt_platform/backend/backend/data/block.py index 14108d71bf1c..e1035e84fe18 100644 --- a/autogpt_platform/backend/backend/data/block.py +++ b/autogpt_platform/backend/backend/data/block.py @@ -15,14 +15,18 @@ import jsonref import jsonschema -from autogpt_libs.supabase_integration_credentials_store.types import Credentials from prisma.models import AgentBlock from pydantic import BaseModel from backend.util import json from backend.util.settings import Config -from .model import CREDENTIALS_FIELD_NAME, ContributorDetails, CredentialsMetaInput +from .model import ( + CREDENTIALS_FIELD_NAME, + ContributorDetails, + Credentials, + CredentialsMetaInput, +) app_config = Config() @@ -54,6 +58,7 @@ class BlockCategory(Enum): DEVELOPER_TOOLS = "Developer tools such as GitHub blocks." DATA = "Block that interacts with structured data." AGENT = "Block that interacts with other agents." + CRM = "Block that interacts with CRM services." def dict(self) -> dict[str, str]: return {"category": self.name, "description": self.value} diff --git a/autogpt_platform/backend/backend/data/block_cost_config.py b/autogpt_platform/backend/backend/data/block_cost_config.py index d8b8878514c0..6b5a12409770 100644 --- a/autogpt_platform/backend/backend/data/block_cost_config.py +++ b/autogpt_platform/backend/backend/data/block_cost_config.py @@ -1,23 +1,10 @@ from typing import Type -from autogpt_libs.supabase_integration_credentials_store.store import ( - anthropic_credentials, - did_credentials, - groq_credentials, - ideogram_credentials, - jina_credentials, - open_router_credentials, - openai_credentials, - replicate_credentials, - revid_credentials, - unreal_credentials, -) - from backend.blocks.ai_music_generator import AIMusicGeneratorBlock from backend.blocks.ai_shortform_video_block import AIShortformVideoCreatorBlock from backend.blocks.ideogram import IdeogramModelBlock from backend.blocks.jina.embeddings import JinaEmbeddingBlock -from backend.blocks.jina.search import SearchTheWebBlock +from backend.blocks.jina.search import ExtractWebsiteContentBlock, SearchTheWebBlock from backend.blocks.llm import ( MODEL_METADATA, AIConversationBlock, @@ -28,11 +15,22 @@ LlmModel, ) from backend.blocks.replicate_flux_advanced import ReplicateFluxAdvancedModelBlock -from backend.blocks.search import ExtractWebsiteContentBlock from backend.blocks.talking_head import CreateTalkingAvatarVideoBlock from backend.blocks.text_to_speech_block import UnrealTextToSpeechBlock from backend.data.block import Block from backend.data.cost import BlockCost, BlockCostType +from backend.integrations.credentials_store import ( + anthropic_credentials, + did_credentials, + groq_credentials, + ideogram_credentials, + jina_credentials, + open_router_credentials, + openai_credentials, + replicate_credentials, + revid_credentials, + unreal_credentials, +) # =============== Configure the cost for each LLM Model call =============== # @@ -72,18 +70,8 @@ LLM_COST = ( + # Anthropic Models [ - BlockCost( - cost_type=BlockCostType.RUN, - cost_filter={ - "model": model, - "api_key": None, # Running LLM with user own API key is free. - }, - cost_amount=cost, - ) - for model, cost in MODEL_COST.items() - ] - + [ BlockCost( cost_type=BlockCostType.RUN, cost_filter={ @@ -99,6 +87,7 @@ for model, cost in MODEL_COST.items() if MODEL_METADATA[model].provider == "anthropic" ] + # OpenAI Models + [ BlockCost( cost_type=BlockCostType.RUN, @@ -115,6 +104,7 @@ for model, cost in MODEL_COST.items() if MODEL_METADATA[model].provider == "openai" ] + # Groq Models + [ BlockCost( cost_type=BlockCostType.RUN, @@ -127,13 +117,6 @@ for model, cost in MODEL_COST.items() if MODEL_METADATA[model].provider == "groq" ] - + [ - BlockCost( - # Default cost is running LlmModel.GPT4O. - cost_amount=MODEL_COST[LlmModel.GPT4O], - cost_filter={"api_key": None}, - ), - ] # Open Router Models + [ BlockCost( @@ -186,7 +169,17 @@ ) ], ExtractWebsiteContentBlock: [ - BlockCost(cost_amount=1, cost_filter={"raw_content": False}) + BlockCost( + cost_amount=1, + cost_filter={ + "raw_content": False, + "credentials": { + "id": jina_credentials.id, + "provider": jina_credentials.provider, + "type": jina_credentials.type, + }, + }, + ) ], IdeogramModelBlock: [ BlockCost( diff --git a/autogpt_platform/backend/backend/data/credit.py b/autogpt_platform/backend/backend/data/credit.py index 4112310cd066..b3f8fbce5d4d 100644 --- a/autogpt_platform/backend/backend/data/credit.py +++ b/autogpt_platform/backend/backend/data/credit.py @@ -70,7 +70,11 @@ class UserCredit(UserCreditBase): async def get_or_refill_credit(self, user_id: str) -> int: cur_time = self.time_now() cur_month = cur_time.replace(day=1, hour=0, minute=0, second=0, microsecond=0) - nxt_month = cur_month.replace(month=cur_month.month + 1) + nxt_month = ( + cur_month.replace(month=cur_month.month + 1) + if cur_month.month < 12 + else cur_month.replace(year=cur_month.year + 1, month=1) + ) user_credit = await UserBlockCredit.prisma().group_by( by=["userId"], @@ -107,8 +111,8 @@ async def get_or_refill_credit(self, user_id: str) -> int: def time_now(): return datetime.now(timezone.utc) - @staticmethod def _block_usage_cost( + self, block: Block, input_data: BlockInput, data_size: float, @@ -119,28 +123,44 @@ def _block_usage_cost( return 0, {} for block_cost in block_costs: - if all( - # None, [], {}, "", are considered the same value. - input_data.get(k) == b or (not input_data.get(k) and not b) - for k, b in block_cost.cost_filter.items() - ): - if block_cost.cost_type == BlockCostType.RUN: - return block_cost.cost_amount, block_cost.cost_filter - - if block_cost.cost_type == BlockCostType.SECOND: - return ( - int(run_time * block_cost.cost_amount), - block_cost.cost_filter, - ) - - if block_cost.cost_type == BlockCostType.BYTE: - return ( - int(data_size * block_cost.cost_amount), - block_cost.cost_filter, - ) + if not self._is_cost_filter_match(block_cost.cost_filter, input_data): + continue + + if block_cost.cost_type == BlockCostType.RUN: + return block_cost.cost_amount, block_cost.cost_filter + + if block_cost.cost_type == BlockCostType.SECOND: + return ( + int(run_time * block_cost.cost_amount), + block_cost.cost_filter, + ) + + if block_cost.cost_type == BlockCostType.BYTE: + return ( + int(data_size * block_cost.cost_amount), + block_cost.cost_filter, + ) return 0, {} + def _is_cost_filter_match( + self, cost_filter: BlockInput, input_data: BlockInput + ) -> bool: + """ + Filter rules: + - If costFilter is an object, then check if costFilter is the subset of inputValues + - Otherwise, check if costFilter is equal to inputValues. + - Undefined, null, and empty string are considered as equal. + """ + if not isinstance(cost_filter, dict) or not isinstance(input_data, dict): + return cost_filter == input_data + + return all( + (not input_data.get(k) and not v) + or (input_data.get(k) and self._is_cost_filter_match(v, input_data[k])) + for k, v in cost_filter.items() + ) + async def spend_credits( self, user_id: str, diff --git a/autogpt_platform/backend/backend/data/db.py b/autogpt_platform/backend/backend/data/db.py index 1bf5d930f61a..4e1ee79737c8 100644 --- a/autogpt_platform/backend/backend/data/db.py +++ b/autogpt_platform/backend/backend/data/db.py @@ -23,15 +23,23 @@ async def connect(): if prisma.is_connected(): return + await prisma.connect() + if not prisma.is_connected(): + raise ConnectionError("Failed to connect to Prisma.") + @conn_retry("Prisma", "Releasing connection") async def disconnect(): if not prisma.is_connected(): return + await prisma.disconnect() + if prisma.is_connected(): + raise ConnectionError("Failed to disconnect from Prisma.") + @asynccontextmanager async def transaction(): diff --git a/autogpt_platform/backend/backend/data/graph.py b/autogpt_platform/backend/backend/data/graph.py index 1081d6b90e7c..dbe7b753b8c8 100644 --- a/autogpt_platform/backend/backend/data/graph.py +++ b/autogpt_platform/backend/backend/data/graph.py @@ -615,14 +615,11 @@ def make_graph_model(creatable_graph: Graph, user_id: str) -> GraphModel: async def fix_llm_provider_credentials(): """Fix node credentials with provider `llm`""" - from autogpt_libs.supabase_integration_credentials_store import ( - SupabaseIntegrationCredentialsStore, - ) + from backend.integrations.credentials_store import IntegrationCredentialsStore - from .redis import get_redis from .user import get_user_integrations - store = SupabaseIntegrationCredentialsStore(get_redis()) + store = IntegrationCredentialsStore() broken_nodes = await prisma.get_client().query_raw( """ diff --git a/autogpt_platform/backend/backend/data/model.py b/autogpt_platform/backend/backend/data/model.py index 9a988133eb59..f8b6781e0ce1 100644 --- a/autogpt_platform/backend/backend/data/model.py +++ b/autogpt_platform/backend/backend/data/model.py @@ -1,10 +1,20 @@ from __future__ import annotations import logging -from typing import Any, Callable, ClassVar, Generic, Optional, TypeVar +from typing import ( + Annotated, + Any, + Callable, + ClassVar, + Generic, + Literal, + Optional, + TypedDict, + TypeVar, +) +from uuid import uuid4 -from autogpt_libs.supabase_integration_credentials_store.types import CredentialsType -from pydantic import BaseModel, Field, GetCoreSchemaHandler +from pydantic import BaseModel, Field, GetCoreSchemaHandler, SecretStr, field_serializer from pydantic_core import ( CoreSchema, PydanticUndefined, @@ -139,6 +149,77 @@ def SchemaField( ) +class _BaseCredentials(BaseModel): + id: str = Field(default_factory=lambda: str(uuid4())) + provider: str + title: Optional[str] + + @field_serializer("*") + def dump_secret_strings(value: Any, _info): + if isinstance(value, SecretStr): + return value.get_secret_value() + return value + + +class OAuth2Credentials(_BaseCredentials): + type: Literal["oauth2"] = "oauth2" + username: Optional[str] + """Username of the third-party service user that these credentials belong to""" + access_token: SecretStr + access_token_expires_at: Optional[int] + """Unix timestamp (seconds) indicating when the access token expires (if at all)""" + refresh_token: Optional[SecretStr] + refresh_token_expires_at: Optional[int] + """Unix timestamp (seconds) indicating when the refresh token expires (if at all)""" + scopes: list[str] + metadata: dict[str, Any] = Field(default_factory=dict) + + def bearer(self) -> str: + return f"Bearer {self.access_token.get_secret_value()}" + + +class APIKeyCredentials(_BaseCredentials): + type: Literal["api_key"] = "api_key" + api_key: SecretStr + expires_at: Optional[int] + """Unix timestamp (seconds) indicating when the API key expires (if at all)""" + + def bearer(self) -> str: + return f"Bearer {self.api_key.get_secret_value()}" + + +Credentials = Annotated[ + OAuth2Credentials | APIKeyCredentials, + Field(discriminator="type"), +] + + +CredentialsType = Literal["api_key", "oauth2"] + + +class OAuthState(BaseModel): + token: str + provider: str + expires_at: int + """Unix timestamp (seconds) indicating when this OAuth state expires""" + scopes: list[str] + + +class UserMetadata(BaseModel): + integration_credentials: list[Credentials] = Field(default_factory=list) + integration_oauth_states: list[OAuthState] = Field(default_factory=list) + + +class UserMetadataRaw(TypedDict, total=False): + integration_credentials: list[dict] + integration_oauth_states: list[dict] + + +class UserIntegrations(BaseModel): + credentials: list[Credentials] = Field(default_factory=list) + oauth_states: list[OAuthState] = Field(default_factory=list) + + CP = TypeVar("CP", bound=str) CT = TypeVar("CT", bound=CredentialsType) diff --git a/autogpt_platform/backend/backend/data/user.py b/autogpt_platform/backend/backend/data/user.py index a9599bfddecd..8602d0f3b136 100644 --- a/autogpt_platform/backend/backend/data/user.py +++ b/autogpt_platform/backend/backend/data/user.py @@ -2,16 +2,12 @@ from typing import Optional, cast from autogpt_libs.auth.models import DEFAULT_USER_ID -from autogpt_libs.supabase_integration_credentials_store.types import ( - UserIntegrations, - UserMetadata, - UserMetadataRaw, -) from fastapi import HTTPException from prisma import Json from prisma.models import User from backend.data.db import prisma +from backend.data.model import UserIntegrations, UserMetadata, UserMetadataRaw from backend.util.encryption import JSONCryptor logger = logging.getLogger(__name__) diff --git a/autogpt_platform/backend/backend/executor/manager.py b/autogpt_platform/backend/backend/executor/manager.py index 46cb554db8ab..c6db354049ca 100644 --- a/autogpt_platform/backend/backend/executor/manager.py +++ b/autogpt_platform/backend/backend/executor/manager.py @@ -725,13 +725,9 @@ def get_port(cls) -> int: return settings.config.execution_manager_port def run_service(self): - from autogpt_libs.supabase_integration_credentials_store import ( - SupabaseIntegrationCredentialsStore, - ) + from backend.integrations.credentials_store import IntegrationCredentialsStore - self.credentials_store = SupabaseIntegrationCredentialsStore( - redis=redis.get_redis() - ) + self.credentials_store = IntegrationCredentialsStore() self.executor = ProcessPoolExecutor( max_workers=self.pool_size, initializer=Executor.on_graph_executor_start, diff --git a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py b/autogpt_platform/backend/backend/integrations/credentials_store.py similarity index 97% rename from autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py rename to autogpt_platform/backend/backend/integrations/credentials_store.py index 7cc731bf10e7..7d539b73c476 100644 --- a/autogpt_platform/autogpt_libs/autogpt_libs/supabase_integration_credentials_store/store.py +++ b/autogpt_platform/backend/backend/integrations/credentials_store.py @@ -5,20 +5,18 @@ from pydantic import SecretStr if TYPE_CHECKING: - from redis import Redis from backend.executor.database import DatabaseManager from autogpt_libs.utils.cache import thread_cached from autogpt_libs.utils.synchronize import RedisKeyedMutex -from .types import ( +from backend.data.model import ( APIKeyCredentials, Credentials, OAuth2Credentials, OAuthState, UserIntegrations, ) - from backend.util.settings import Settings settings = Settings() @@ -109,9 +107,11 @@ ] -class SupabaseIntegrationCredentialsStore: - def __init__(self, redis: "Redis"): - self.locks = RedisKeyedMutex(redis) +class IntegrationCredentialsStore: + def __init__(self): + from backend.data.redis import get_redis + + self.locks = RedisKeyedMutex(get_redis()) @property @thread_cached @@ -305,5 +305,5 @@ def _get_user_integrations(self, user_id: str) -> UserIntegrations: return integrations def locked_user_integrations(self, user_id: str): - key = (self.db_manager, f"user:{user_id}", "integrations") + key = (f"user:{user_id}", "integrations") return self.locks.locked(key) diff --git a/autogpt_platform/backend/backend/integrations/creds_manager.py b/autogpt_platform/backend/backend/integrations/creds_manager.py index 0fee2e3a8104..7cbf8f4af7f1 100644 --- a/autogpt_platform/backend/backend/integrations/creds_manager.py +++ b/autogpt_platform/backend/backend/integrations/creds_manager.py @@ -2,14 +2,12 @@ from contextlib import contextmanager from datetime import datetime -from autogpt_libs.supabase_integration_credentials_store import ( - Credentials, - SupabaseIntegrationCredentialsStore, -) from autogpt_libs.utils.synchronize import RedisKeyedMutex from redis.lock import Lock as RedisLock from backend.data import redis +from backend.data.model import Credentials +from backend.integrations.credentials_store import IntegrationCredentialsStore from backend.integrations.oauth import HANDLERS_BY_NAME, BaseOAuthHandler from backend.util.exceptions import MissingConfigError from backend.util.settings import Settings @@ -53,7 +51,7 @@ class IntegrationCredentialsManager: def __init__(self): redis_conn = redis.get_redis() self._locks = RedisKeyedMutex(redis_conn) - self.store = SupabaseIntegrationCredentialsStore(redis=redis_conn) + self.store = IntegrationCredentialsStore() def create(self, user_id: str, credentials: Credentials) -> None: return self.store.add_creds(user_id, credentials) @@ -130,7 +128,6 @@ def delete(self, user_id: str, credentials_id: str) -> None: def _acquire_lock(self, user_id: str, credentials_id: str, *args: str) -> RedisLock: key = ( - self.store.db_manager, f"user:{user_id}", f"credentials:{credentials_id}", *args, diff --git a/autogpt_platform/backend/backend/integrations/oauth/base.py b/autogpt_platform/backend/backend/integrations/oauth/base.py index a12200af6590..ad5433d734ec 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/base.py +++ b/autogpt_platform/backend/backend/integrations/oauth/base.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from typing import ClassVar -from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials +from backend.data.model import OAuth2Credentials logger = logging.getLogger(__name__) diff --git a/autogpt_platform/backend/backend/integrations/oauth/github.py b/autogpt_platform/backend/backend/integrations/oauth/github.py index 402dbe21a9f8..ed883b63205f 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/github.py +++ b/autogpt_platform/backend/backend/integrations/oauth/github.py @@ -2,8 +2,7 @@ from typing import Optional from urllib.parse import urlencode -from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials - +from backend.data.model import OAuth2Credentials from backend.util.request import requests from .base import BaseOAuthHandler diff --git a/autogpt_platform/backend/backend/integrations/oauth/google.py b/autogpt_platform/backend/backend/integrations/oauth/google.py index 1f410b5e5920..13175b0b469f 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/google.py +++ b/autogpt_platform/backend/backend/integrations/oauth/google.py @@ -1,6 +1,5 @@ import logging -from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials from google.auth.external_account_authorized_user import ( Credentials as ExternalAccountCredentials, ) @@ -9,6 +8,8 @@ from google_auth_oauthlib.flow import Flow from pydantic import SecretStr +from backend.data.model import OAuth2Credentials + from .base import BaseOAuthHandler logger = logging.getLogger(__name__) diff --git a/autogpt_platform/backend/backend/integrations/oauth/notion.py b/autogpt_platform/backend/backend/integrations/oauth/notion.py index 75f7a91eeef1..7f5458ae9a10 100644 --- a/autogpt_platform/backend/backend/integrations/oauth/notion.py +++ b/autogpt_platform/backend/backend/integrations/oauth/notion.py @@ -1,8 +1,7 @@ from base64 import b64encode from urllib.parse import urlencode -from autogpt_libs.supabase_integration_credentials_store import OAuth2Credentials - +from backend.data.model import OAuth2Credentials from backend.util.request import requests from .base import BaseOAuthHandler diff --git a/autogpt_platform/backend/backend/integrations/webhooks/base.py b/autogpt_platform/backend/backend/integrations/webhooks/base.py index b30f419a03e8..61a07ce20353 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/base.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/base.py @@ -4,11 +4,11 @@ from typing import ClassVar, Generic, TypeVar from uuid import uuid4 -from autogpt_libs.supabase_integration_credentials_store import Credentials from fastapi import Request from strenum import StrEnum from backend.data import integrations +from backend.data.model import Credentials from backend.util.exceptions import MissingConfigError from backend.util.settings import Config diff --git a/autogpt_platform/backend/backend/integrations/webhooks/github.py b/autogpt_platform/backend/backend/integrations/webhooks/github.py index 25152caff438..2393437d209a 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/github.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/github.py @@ -3,11 +3,11 @@ import logging import requests -from autogpt_libs.supabase_integration_credentials_store import Credentials from fastapi import HTTPException, Request from strenum import StrEnum from backend.data import integrations +from backend.data.model import Credentials from .base import BaseWebhooksManager diff --git a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py index 1f6351d5fab2..363bf535c501 100644 --- a/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py +++ b/autogpt_platform/backend/backend/integrations/webhooks/graph_lifecycle_hooks.py @@ -7,9 +7,8 @@ from backend.integrations.webhooks import WEBHOOK_MANAGERS_BY_NAME if TYPE_CHECKING: - from autogpt_libs.supabase_integration_credentials_store.types import Credentials - from backend.data.graph import GraphModel, NodeModel + from backend.data.model import Credentials from .base import BaseWebhooksManager diff --git a/autogpt_platform/backend/backend/rest.py b/autogpt_platform/backend/backend/rest.py index f56deaa1d26e..e0da452ca2be 100644 --- a/autogpt_platform/backend/backend/rest.py +++ b/autogpt_platform/backend/backend/rest.py @@ -1,5 +1,5 @@ from backend.app import run_processes -from backend.executor import ExecutionScheduler +from backend.executor import DatabaseManager, ExecutionScheduler from backend.server.rest_api import AgentServer @@ -8,6 +8,7 @@ def main(): Run all the processes required for the AutoGPT-server REST API. """ run_processes( + DatabaseManager(), ExecutionScheduler(), AgentServer(), ) diff --git a/autogpt_platform/backend/backend/server/integrations/router.py b/autogpt_platform/backend/backend/server/integrations/router.py index ecf28cedde97..4220ac3c0311 100644 --- a/autogpt_platform/backend/backend/server/integrations/router.py +++ b/autogpt_platform/backend/backend/server/integrations/router.py @@ -1,12 +1,6 @@ import logging from typing import Annotated, Literal -from autogpt_libs.supabase_integration_credentials_store.types import ( - APIKeyCredentials, - Credentials, - CredentialsType, - OAuth2Credentials, -) from fastapi import APIRouter, Body, Depends, HTTPException, Path, Query, Request from pydantic import BaseModel, Field, SecretStr @@ -18,6 +12,12 @@ listen_for_webhook_event, publish_webhook_event, ) +from backend.data.model import ( + APIKeyCredentials, + Credentials, + CredentialsType, + OAuth2Credentials, +) from backend.executor.manager import ExecutionManager from backend.integrations.creds_manager import IntegrationCredentialsManager from backend.integrations.oauth import HANDLERS_BY_NAME, BaseOAuthHandler @@ -65,6 +65,7 @@ def login( class CredentialsMetaResponse(BaseModel): id: str + provider: str type: CredentialsType title: str | None scopes: list[str] | None @@ -119,6 +120,7 @@ def callback( ) return CredentialsMetaResponse( id=credentials.id, + provider=credentials.provider, type=credentials.type, title=credentials.title, scopes=credentials.scopes, @@ -126,8 +128,26 @@ def callback( ) -@router.get("/{provider}/credentials") +@router.get("/credentials") def list_credentials( + user_id: Annotated[str, Depends(get_user_id)], +) -> list[CredentialsMetaResponse]: + credentials = creds_manager.store.get_all_creds(user_id) + return [ + CredentialsMetaResponse( + id=cred.id, + provider=cred.provider, + type=cred.type, + title=cred.title, + scopes=cred.scopes if isinstance(cred, OAuth2Credentials) else None, + username=cred.username if isinstance(cred, OAuth2Credentials) else None, + ) + for cred in credentials + ] + + +@router.get("/{provider}/credentials") +def list_credentials_by_provider( provider: Annotated[str, Path(title="The provider to list credentials for")], user_id: Annotated[str, Depends(get_user_id)], ) -> list[CredentialsMetaResponse]: @@ -135,6 +155,7 @@ def list_credentials( return [ CredentialsMetaResponse( id=cred.id, + provider=cred.provider, type=cred.type, title=cred.title, scopes=cred.scopes if isinstance(cred, OAuth2Credentials) else None, diff --git a/autogpt_platform/backend/backend/server/rest_api.py b/autogpt_platform/backend/backend/server/rest_api.py index 06e7dc64a844..d0ba73f65a07 100644 --- a/autogpt_platform/backend/backend/server/rest_api.py +++ b/autogpt_platform/backend/backend/server/rest_api.py @@ -6,6 +6,10 @@ import fastapi.responses import starlette.middleware.cors import uvicorn +from autogpt_libs.feature_flag.client import ( + initialize_launchdarkly, + shutdown_launchdarkly, +) import backend.data.block import backend.data.db @@ -18,6 +22,8 @@ settings = backend.util.settings.Settings() logger = logging.getLogger(__name__) +logging.getLogger("autogpt_libs").setLevel(logging.INFO) + @contextlib.asynccontextmanager async def lifespan_context(app: fastapi.FastAPI): @@ -25,7 +31,9 @@ async def lifespan_context(app: fastapi.FastAPI): await backend.data.block.initialize_blocks() await backend.data.user.migrate_and_encrypt_user_integrations() await backend.data.graph.fix_llm_provider_credentials() + initialize_launchdarkly() yield + shutdown_launchdarkly() await backend.data.db.disconnect() diff --git a/autogpt_platform/backend/backend/server/routers/v1.py b/autogpt_platform/backend/backend/server/routers/v1.py index f1f1fda385bd..c600c067c4db 100644 --- a/autogpt_platform/backend/backend/server/routers/v1.py +++ b/autogpt_platform/backend/backend/server/routers/v1.py @@ -5,6 +5,7 @@ import pydantic from autogpt_libs.auth.middleware import auth_middleware +from autogpt_libs.feature_flag.client import feature_flag from autogpt_libs.utils.cache import thread_cached from fastapi import APIRouter, Depends, HTTPException from typing_extensions import Optional, TypedDict @@ -47,7 +48,7 @@ from backend.util.settings import Settings if TYPE_CHECKING: - from autogpt_libs.supabase_integration_credentials_store.types import Credentials + from backend.data.model import Credentials @thread_cached @@ -563,10 +564,11 @@ def get_execution_schedules( @v1_router.post( "/api-keys", - response_model=CreateAPIKeyResponse, + response_model=list[CreateAPIKeyResponse] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def create_api_key( request: CreateAPIKeyRequest, user_id: Annotated[str, Depends(get_user_id)] ) -> CreateAPIKeyResponse: @@ -586,10 +588,11 @@ async def create_api_key( @v1_router.get( "/api-keys", - response_model=list[APIKeyWithoutHash], + response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def get_api_keys( user_id: Annotated[str, Depends(get_user_id)] ) -> list[APIKeyWithoutHash]: @@ -603,10 +606,11 @@ async def get_api_keys( @v1_router.get( "/api-keys/{key_id}", - response_model=APIKeyWithoutHash, + response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def get_api_key( key_id: str, user_id: Annotated[str, Depends(get_user_id)] ) -> APIKeyWithoutHash: @@ -623,10 +627,11 @@ async def get_api_key( @v1_router.delete( "/api-keys/{key_id}", - response_model=APIKeyWithoutHash, + response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def delete_api_key( key_id: str, user_id: Annotated[str, Depends(get_user_id)] ) -> Optional[APIKeyWithoutHash]: @@ -644,10 +649,11 @@ async def delete_api_key( @v1_router.post( "/api-keys/{key_id}/suspend", - response_model=APIKeyWithoutHash, + response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def suspend_key( key_id: str, user_id: Annotated[str, Depends(get_user_id)] ) -> Optional[APIKeyWithoutHash]: @@ -665,10 +671,11 @@ async def suspend_key( @v1_router.put( "/api-keys/{key_id}/permissions", - response_model=APIKeyWithoutHash, + response_model=list[APIKeyWithoutHash] | dict[str, str], tags=["api-keys"], dependencies=[Depends(auth_middleware)], ) +@feature_flag("api-keys-enabled") async def update_permissions( key_id: str, request: UpdatePermissionsRequest, diff --git a/autogpt_platform/backend/backend/util/retry.py b/autogpt_platform/backend/backend/util/retry.py index bbc739e412ce..c1adab5caf26 100644 --- a/autogpt_platform/backend/backend/util/retry.py +++ b/autogpt_platform/backend/backend/util/retry.py @@ -1,3 +1,4 @@ +import asyncio import logging import os import threading @@ -20,7 +21,14 @@ def _log_prefix(resource_name: str, conn_id: str): return f"[PID-{os.getpid()}|THREAD-{threading.get_native_id()}|{get_service_name()}|{resource_name}-{conn_id}]" -def conn_retry(resource_name: str, action_name: str, max_retry: int = 5): +def conn_retry( + resource_name: str, + action_name: str, + max_retry: int = 5, + multiplier: int = 1, + min_wait: float = 1, + max_wait: float = 30, +): conn_id = str(uuid4()) def on_retry(retry_state): @@ -29,27 +37,39 @@ def on_retry(retry_state): logger.error(f"{prefix} {action_name} failed: {exception}. Retrying now...") def decorator(func): + is_coroutine = asyncio.iscoroutinefunction(func) + retry_decorator = retry( + stop=stop_after_attempt(max_retry + 1), + wait=wait_exponential(multiplier=multiplier, min=min_wait, max=max_wait), + before_sleep=on_retry, + reraise=True, + ) + wrapped_func = retry_decorator(func) + @wraps(func) - def wrapper(*args, **kwargs): + def sync_wrapper(*args, **kwargs): prefix = _log_prefix(resource_name, conn_id) logger.info(f"{prefix} {action_name} started...") + try: + result = wrapped_func(*args, **kwargs) + logger.info(f"{prefix} {action_name} completed successfully.") + return result + except Exception as e: + logger.error(f"{prefix} {action_name} failed after retries: {e}") + raise - # Define the retrying strategy - retrying_func = retry( - stop=stop_after_attempt(max_retry + 1), - wait=wait_exponential(multiplier=1, min=1, max=30), - before_sleep=on_retry, - reraise=True, - )(func) - + @wraps(func) + async def async_wrapper(*args, **kwargs): + prefix = _log_prefix(resource_name, conn_id) + logger.info(f"{prefix} {action_name} started...") try: - result = retrying_func(*args, **kwargs) + result = await wrapped_func(*args, **kwargs) logger.info(f"{prefix} {action_name} completed successfully.") return result except Exception as e: logger.error(f"{prefix} {action_name} failed after retries: {e}") raise - return wrapper + return async_wrapper if is_coroutine else sync_wrapper return decorator diff --git a/autogpt_platform/backend/backend/util/settings.py b/autogpt_platform/backend/backend/util/settings.py index 34ca9336d45d..63721653a96b 100644 --- a/autogpt_platform/backend/backend/util/settings.py +++ b/autogpt_platform/backend/backend/util/settings.py @@ -290,6 +290,8 @@ class Secrets(UpdateTrackingModel["Secrets"], BaseSettings): jina_api_key: str = Field(default="", description="Jina API Key") unreal_speech_api_key: str = Field(default="", description="Unreal Speech API Key") + fal_key: str = Field(default="", description="FAL API key") + # Add more secret fields as needed model_config = SettingsConfigDict( diff --git a/autogpt_platform/backend/linter.py b/autogpt_platform/backend/linter.py index 83c574b03494..9bba9d1963b6 100644 --- a/autogpt_platform/backend/linter.py +++ b/autogpt_platform/backend/linter.py @@ -2,6 +2,7 @@ import subprocess directory = os.path.dirname(os.path.realpath(__file__)) +target_dirs = ["../backend", "../autogpt_libs"] def run(*command: str) -> None: @@ -11,17 +12,17 @@ def run(*command: str) -> None: def lint(): try: - run("ruff", "check", ".", "--exit-zero") + run("ruff", "check", *target_dirs, "--exit-zero") run("isort", "--diff", "--check", "--profile", "black", ".") run("black", "--diff", "--check", ".") - run("pyright") + run("pyright", *target_dirs) except subprocess.CalledProcessError as e: print("Lint failed, try running `poetry run format` to fix the issues: ", e) raise e def format(): - run("ruff", "check", "--fix", ".") + run("ruff", "check", "--fix", *target_dirs) run("isort", "--profile", "black", ".") run("black", ".") - run("pyright", ".") + run("pyright", *target_dirs) diff --git a/autogpt_platform/backend/poetry.lock b/autogpt_platform/backend/poetry.lock index 619b7b8c7dd0..02674c24ec38 100644 --- a/autogpt_platform/backend/poetry.lock +++ b/autogpt_platform/backend/poetry.lock @@ -2,17 +2,18 @@ [[package]] name = "aio-pika" -version = "9.4.3" +version = "9.5.0" description = "Wrapper around the aiormq for asyncio and humans" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "aio_pika-9.4.3-py3-none-any.whl", hash = "sha256:f1423d2d5a8b7315d144efe1773763bf687ac17aa1535385982687e9e5ed49bb"}, - {file = "aio_pika-9.4.3.tar.gz", hash = "sha256:fd2b1fce25f6ed5203ef1dd554dc03b90c9a46a64aaf758d032d78dc31e5295d"}, + {file = "aio_pika-9.5.0-py3-none-any.whl", hash = "sha256:7e03b80fab5a0d354dca45fb5ac95f074b87c639db34c6a1962cabe0fd95bd56"}, + {file = "aio_pika-9.5.0.tar.gz", hash = "sha256:d45d49e6543bcdfd2fe4b1a0ee59d931c15a96e99497bc599cf0fddb94061925"}, ] [package.dependencies] aiormq = ">=6.8.0,<6.9.0" +exceptiongroup = ">=1,<2" yarl = "*" [[package]] @@ -226,28 +227,27 @@ trio = ["trio (>=0.26.1)"] [[package]] name = "apscheduler" -version = "3.10.4" +version = "3.11.0" description = "In-process task scheduler with Cron-like capabilities" optional = false -python-versions = ">=3.6" +python-versions = ">=3.8" files = [ - {file = "APScheduler-3.10.4-py3-none-any.whl", hash = "sha256:fb91e8a768632a4756a585f79ec834e0e27aad5860bac7eaa523d9ccefd87661"}, - {file = "APScheduler-3.10.4.tar.gz", hash = "sha256:e6df071b27d9be898e486bc7940a7be50b4af2e9da7c08f0744a96d4bd4cef4a"}, + {file = "APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da"}, + {file = "apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133"}, ] [package.dependencies] -pytz = "*" -six = ">=1.4.0" -tzlocal = ">=2.0,<3.dev0 || >=4.dev0" +tzlocal = ">=3.0" [package.extras] -doc = ["sphinx", "sphinx-rtd-theme"] +doc = ["packaging", "sphinx", "sphinx-rtd-theme (>=1.3.0)"] +etcd = ["etcd3", "protobuf (<=3.21.0)"] gevent = ["gevent"] mongodb = ["pymongo (>=3.0)"] redis = ["redis (>=3.0)"] rethinkdb = ["rethinkdb (>=2.4.0)"] sqlalchemy = ["sqlalchemy (>=1.4)"] -testing = ["pytest", "pytest-asyncio", "pytest-cov", "pytest-tornado5"] +test = ["APScheduler[etcd,mongodb,redis,rethinkdb,sqlalchemy,tornado,zookeeper]", "PySide6", "anyio (>=4.5.2)", "gevent", "pytest", "pytz", "twisted"] tornado = ["tornado (>=4.3)"] twisted = ["twisted"] zookeeper = ["kazoo"] @@ -298,6 +298,8 @@ google-cloud-logging = "^3.11.3" pydantic = "^2.9.2" pydantic-settings = "^2.6.1" pyjwt = "^2.10.0" +pytest-asyncio = "^0.24.0" +pytest-mock = "^3.14.0" python-dotenv = "^1.0.1" supabase = "^2.10.0" @@ -642,20 +644,20 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "deprecated" -version = "1.2.14" +version = "1.2.15" description = "Python @deprecated decorator to deprecate old python classes, functions or methods." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" files = [ - {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, - {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, + {file = "Deprecated-1.2.15-py2.py3-none-any.whl", hash = "sha256:353bc4a8ac4bfc96800ddab349d89c25dec1079f65fd53acdcc1e0b975b21320"}, + {file = "deprecated-1.2.15.tar.gz", hash = "sha256:683e561a90de76239796e6b6feac66b99030d2dd3fcf61ef996330f14bbb9b0d"}, ] [package.dependencies] wrapt = ">=1.10,<2" [package.extras] -dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "jinja2 (>=3.0.3,<3.1.0)", "setuptools", "sphinx (<2)", "tox"] [[package]] name = "deprecation" @@ -732,13 +734,13 @@ tests = ["coverage", "coveralls", "dill", "mock", "nose"] [[package]] name = "fastapi" -version = "0.115.4" +version = "0.115.5" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.8" files = [ - {file = "fastapi-0.115.4-py3-none-any.whl", hash = "sha256:0b504a063ffb3cf96a5e27dc1bc32c80ca743a2528574f9cdc77daa2d31b4742"}, - {file = "fastapi-0.115.4.tar.gz", hash = "sha256:db653475586b091cb8b2fec2ac54a680ac6a158e07406e1abae31679e8826349"}, + {file = "fastapi-0.115.5-py3-none-any.whl", hash = "sha256:596b95adbe1474da47049e802f9a65ab2ffa9c2b07e7efee70eb8a66c9f2f796"}, + {file = "fastapi-0.115.5.tar.gz", hash = "sha256:0e7a4d0dc0d01c68df21887cce0945e72d3c48b9f4f79dfe7a7d53aa08fbb289"}, ] [package.dependencies] @@ -899,13 +901,13 @@ grpcio-gcp = ["grpcio-gcp (>=0.2.2,<1.0.dev0)"] [[package]] name = "google-api-python-client" -version = "2.151.0" +version = "2.154.0" description = "Google API Client Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "google_api_python_client-2.151.0-py2.py3-none-any.whl", hash = "sha256:4427b2f47cd88b0355d540c2c52215f68c337f3bc9d6aae1ceeae4525977504c"}, - {file = "google_api_python_client-2.151.0.tar.gz", hash = "sha256:a9d26d630810ed4631aea21d1de3e42072f98240aaf184a8a1a874a371115034"}, + {file = "google_api_python_client-2.154.0-py2.py3-none-any.whl", hash = "sha256:a521bbbb2ec0ba9d6f307cdd64ed6e21eeac372d1bd7493a4ab5022941f784ad"}, + {file = "google_api_python_client-2.154.0.tar.gz", hash = "sha256:1b420062e03bfcaa1c79e2e00a612d29a6a934151ceb3d272fe150a656dc8f17"}, ] [package.dependencies] @@ -973,19 +975,22 @@ tool = ["click (>=6.0.0)"] [[package]] name = "google-cloud-appengine-logging" -version = "1.4.5" +version = "1.5.0" description = "Google Cloud Appengine Logging API client library" optional = false python-versions = ">=3.7" files = [ - {file = "google_cloud_appengine_logging-1.4.5-py2.py3-none-any.whl", hash = "sha256:344e0244404049b42164e4d6dc718ca2c81b393d066956e7cb85fd9407ed9c48"}, - {file = "google_cloud_appengine_logging-1.4.5.tar.gz", hash = "sha256:de7d766e5d67b19fc5833974b505b32d2a5bbdfb283fd941e320e7cfdae4cb83"}, + {file = "google_cloud_appengine_logging-1.5.0-py2.py3-none-any.whl", hash = "sha256:81e36606e13c377c4898c918542888abb7a6896837ac5f559011c7729fc63d8a"}, + {file = "google_cloud_appengine_logging-1.5.0.tar.gz", hash = "sha256:39a2df694d97981ed00ef5df541f7cfcca920a92496707557f2b07bb7ba9d67a"}, ] [package.dependencies] google-api-core = {version = ">=1.34.1,<2.0.dev0 || >=2.11.dev0,<3.0.0dev", extras = ["grpc"]} google-auth = ">=2.14.1,<2.24.0 || >2.24.0,<2.25.0 || >2.25.0,<3.0.0dev" -proto-plus = ">=1.22.3,<2.0.0dev" +proto-plus = [ + {version = ">=1.22.3,<2.0.0dev", markers = "python_version < \"3.13\""}, + {version = ">=1.25.0,<2.0.0dev", markers = "python_version >= \"3.13\""}, +] protobuf = ">=3.20.2,<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,<6.0.0dev" [[package]] @@ -1180,13 +1185,13 @@ test = ["objgraph", "psutil"] [[package]] name = "groq" -version = "0.11.0" +version = "0.12.0" description = "The official Python library for the groq API" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "groq-0.11.0-py3-none-any.whl", hash = "sha256:e328531c979542e563668c62260aec13b43a6ee0ca9e2fb22dff1d26f8c8ce54"}, - {file = "groq-0.11.0.tar.gz", hash = "sha256:dbb9aefedf388ddd4801ec7bf3eba7f5edb67948fec0cd2829d97244059f42a7"}, + {file = "groq-0.12.0-py3-none-any.whl", hash = "sha256:e8aa1529f82a01b2d15394b7ea242af9ee9387f65bdd1b91ce9a10f5a911dac1"}, + {file = "groq-0.12.0.tar.gz", hash = "sha256:569229e2dadfc428b0df3d2987407691a4e3bc035b5849a65ef4909514a4605e"}, ] [package.dependencies] @@ -1215,85 +1220,85 @@ protobuf = ">=3.20.2,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4 [[package]] name = "grpcio" -version = "1.66.2" +version = "1.68.0" description = "HTTP/2-based RPC framework" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio-1.66.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:fe96281713168a3270878255983d2cb1a97e034325c8c2c25169a69289d3ecfa"}, - {file = "grpcio-1.66.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:73fc8f8b9b5c4a03e802b3cd0c18b2b06b410d3c1dcbef989fdeb943bd44aff7"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:03b0b307ba26fae695e067b94cbb014e27390f8bc5ac7a3a39b7723fed085604"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d69ce1f324dc2d71e40c9261d3fdbe7d4c9d60f332069ff9b2a4d8a257c7b2b"}, - {file = "grpcio-1.66.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05bc2ceadc2529ab0b227b1310d249d95d9001cd106aa4d31e8871ad3c428d73"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ac475e8da31484efa25abb774674d837b343afb78bb3bcdef10f81a93e3d6bf"}, - {file = "grpcio-1.66.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0be4e0490c28da5377283861bed2941d1d20ec017ca397a5df4394d1c31a9b50"}, - {file = "grpcio-1.66.2-cp310-cp310-win32.whl", hash = "sha256:4e504572433f4e72b12394977679161d495c4c9581ba34a88d843eaf0f2fbd39"}, - {file = "grpcio-1.66.2-cp310-cp310-win_amd64.whl", hash = "sha256:2018b053aa15782db2541ca01a7edb56a0bf18c77efed975392583725974b249"}, - {file = "grpcio-1.66.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:2335c58560a9e92ac58ff2bc5649952f9b37d0735608242973c7a8b94a6437d8"}, - {file = "grpcio-1.66.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:45a3d462826f4868b442a6b8fdbe8b87b45eb4f5b5308168c156b21eca43f61c"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:a9539f01cb04950fd4b5ab458e64a15f84c2acc273670072abe49a3f29bbad54"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce89f5876662f146d4c1f695dda29d4433a5d01c8681fbd2539afff535da14d4"}, - {file = "grpcio-1.66.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d25a14af966438cddf498b2e338f88d1c9706f3493b1d73b93f695c99c5f0e2a"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6001e575b8bbd89eee11960bb640b6da6ae110cf08113a075f1e2051cc596cae"}, - {file = "grpcio-1.66.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4ea1d062c9230278793820146c95d038dc0f468cbdd172eec3363e42ff1c7d01"}, - {file = "grpcio-1.66.2-cp311-cp311-win32.whl", hash = "sha256:38b68498ff579a3b1ee8f93a05eb48dc2595795f2f62716e797dc24774c1aaa8"}, - {file = "grpcio-1.66.2-cp311-cp311-win_amd64.whl", hash = "sha256:6851de821249340bdb100df5eacfecfc4e6075fa85c6df7ee0eb213170ec8e5d"}, - {file = "grpcio-1.66.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:802d84fd3d50614170649853d121baaaa305de7b65b3e01759247e768d691ddf"}, - {file = "grpcio-1.66.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:80fd702ba7e432994df208f27514280b4b5c6843e12a48759c9255679ad38db8"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:12fda97ffae55e6526825daf25ad0fa37483685952b5d0f910d6405c87e3adb6"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:950da58d7d80abd0ea68757769c9db0a95b31163e53e5bb60438d263f4bed7b7"}, - {file = "grpcio-1.66.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e636ce23273683b00410f1971d209bf3689238cf5538d960adc3cdfe80dd0dbd"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:a917d26e0fe980b0ac7bfcc1a3c4ad6a9a4612c911d33efb55ed7833c749b0ee"}, - {file = "grpcio-1.66.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:49f0ca7ae850f59f828a723a9064cadbed90f1ece179d375966546499b8a2c9c"}, - {file = "grpcio-1.66.2-cp312-cp312-win32.whl", hash = "sha256:31fd163105464797a72d901a06472860845ac157389e10f12631025b3e4d0453"}, - {file = "grpcio-1.66.2-cp312-cp312-win_amd64.whl", hash = "sha256:ff1f7882e56c40b0d33c4922c15dfa30612f05fb785074a012f7cda74d1c3679"}, - {file = "grpcio-1.66.2-cp313-cp313-linux_armv7l.whl", hash = "sha256:3b00efc473b20d8bf83e0e1ae661b98951ca56111feb9b9611df8efc4fe5d55d"}, - {file = "grpcio-1.66.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1caa38fb22a8578ab8393da99d4b8641e3a80abc8fd52646f1ecc92bcb8dee34"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:c408f5ef75cfffa113cacd8b0c0e3611cbfd47701ca3cdc090594109b9fcbaed"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c806852deaedee9ce8280fe98955c9103f62912a5b2d5ee7e3eaa284a6d8d8e7"}, - {file = "grpcio-1.66.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f145cc21836c332c67baa6fc81099d1d27e266401565bf481948010d6ea32d46"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:73e3b425c1e155730273f73e419de3074aa5c5e936771ee0e4af0814631fb30a"}, - {file = "grpcio-1.66.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:9c509a4f78114cbc5f0740eb3d7a74985fd2eff022971bc9bc31f8bc93e66a3b"}, - {file = "grpcio-1.66.2-cp313-cp313-win32.whl", hash = "sha256:20657d6b8cfed7db5e11b62ff7dfe2e12064ea78e93f1434d61888834bc86d75"}, - {file = "grpcio-1.66.2-cp313-cp313-win_amd64.whl", hash = "sha256:fb70487c95786e345af5e854ffec8cb8cc781bcc5df7930c4fbb7feaa72e1cdf"}, - {file = "grpcio-1.66.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:a18e20d8321c6400185b4263e27982488cb5cdd62da69147087a76a24ef4e7e3"}, - {file = "grpcio-1.66.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:02697eb4a5cbe5a9639f57323b4c37bcb3ab2d48cec5da3dc2f13334d72790dd"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:99a641995a6bc4287a6315989ee591ff58507aa1cbe4c2e70d88411c4dcc0839"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ed71e81782966ffead60268bbda31ea3f725ebf8aa73634d5dda44f2cf3fb9c"}, - {file = "grpcio-1.66.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbd27c24a4cc5e195a7f56cfd9312e366d5d61b86e36d46bbe538457ea6eb8dd"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d9a9724a156c8ec6a379869b23ba3323b7ea3600851c91489b871e375f710bc8"}, - {file = "grpcio-1.66.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d8d4732cc5052e92cea2f78b233c2e2a52998ac40cd651f40e398893ad0d06ec"}, - {file = "grpcio-1.66.2-cp38-cp38-win32.whl", hash = "sha256:7b2c86457145ce14c38e5bf6bdc19ef88e66c5fee2c3d83285c5aef026ba93b3"}, - {file = "grpcio-1.66.2-cp38-cp38-win_amd64.whl", hash = "sha256:e88264caad6d8d00e7913996030bac8ad5f26b7411495848cc218bd3a9040b6c"}, - {file = "grpcio-1.66.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:c400ba5675b67025c8a9f48aa846f12a39cf0c44df5cd060e23fda5b30e9359d"}, - {file = "grpcio-1.66.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:66a0cd8ba6512b401d7ed46bb03f4ee455839957f28b8d61e7708056a806ba6a"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:06de8ec0bd71be123eec15b0e0d457474931c2c407869b6c349bd9bed4adbac3"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fb57870449dfcfac428afbb5a877829fcb0d6db9d9baa1148705739e9083880e"}, - {file = "grpcio-1.66.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b672abf90a964bfde2d0ecbce30f2329a47498ba75ce6f4da35a2f4532b7acbc"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ad2efdbe90c73b0434cbe64ed372e12414ad03c06262279b104a029d1889d13e"}, - {file = "grpcio-1.66.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9c3a99c519f4638e700e9e3f83952e27e2ea10873eecd7935823dab0c1c9250e"}, - {file = "grpcio-1.66.2-cp39-cp39-win32.whl", hash = "sha256:78fa51ebc2d9242c0fc5db0feecc57a9943303b46664ad89921f5079e2e4ada7"}, - {file = "grpcio-1.66.2-cp39-cp39-win_amd64.whl", hash = "sha256:728bdf36a186e7f51da73be7f8d09457a03061be848718d0edf000e709418987"}, - {file = "grpcio-1.66.2.tar.gz", hash = "sha256:563588c587b75c34b928bc428548e5b00ea38c46972181a4d8b75ba7e3f24231"}, -] - -[package.extras] -protobuf = ["grpcio-tools (>=1.66.2)"] + {file = "grpcio-1.68.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:619b5d0f29f4f5351440e9343224c3e19912c21aeda44e0c49d0d147a8d01544"}, + {file = "grpcio-1.68.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:a59f5822f9459bed098ffbceb2713abbf7c6fd13f2b9243461da5c338d0cd6c3"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:c03d89df516128febc5a7e760d675b478ba25802447624edf7aa13b1e7b11e2a"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44bcbebb24363d587472089b89e2ea0ab2e2b4df0e4856ba4c0b087c82412121"}, + {file = "grpcio-1.68.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79f81b7fbfb136247b70465bd836fa1733043fdee539cd6031cb499e9608a110"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:88fb2925789cfe6daa20900260ef0a1d0a61283dfb2d2fffe6194396a354c618"}, + {file = "grpcio-1.68.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:99f06232b5c9138593ae6f2e355054318717d32a9c09cdc5a2885540835067a1"}, + {file = "grpcio-1.68.0-cp310-cp310-win32.whl", hash = "sha256:a6213d2f7a22c3c30a479fb5e249b6b7e648e17f364598ff64d08a5136fe488b"}, + {file = "grpcio-1.68.0-cp310-cp310-win_amd64.whl", hash = "sha256:15327ab81131ef9b94cb9f45b5bd98803a179c7c61205c8c0ac9aff9d6c4e82a"}, + {file = "grpcio-1.68.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:3b2b559beb2d433129441783e5f42e3be40a9e1a89ec906efabf26591c5cd415"}, + {file = "grpcio-1.68.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e46541de8425a4d6829ac6c5d9b16c03c292105fe9ebf78cb1c31e8d242f9155"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:c1245651f3c9ea92a2db4f95d37b7597db6b246d5892bca6ee8c0e90d76fb73c"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f1931c7aa85be0fa6cea6af388e576f3bf6baee9e5d481c586980c774debcb4"}, + {file = "grpcio-1.68.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8b0ff09c81e3aded7a183bc6473639b46b6caa9c1901d6f5e2cba24b95e59e30"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:8c73f9fbbaee1a132487e31585aa83987ddf626426d703ebcb9a528cf231c9b1"}, + {file = "grpcio-1.68.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6b2f98165ea2790ea159393a2246b56f580d24d7da0d0342c18a085299c40a75"}, + {file = "grpcio-1.68.0-cp311-cp311-win32.whl", hash = "sha256:e1e7ed311afb351ff0d0e583a66fcb39675be112d61e7cfd6c8269884a98afbc"}, + {file = "grpcio-1.68.0-cp311-cp311-win_amd64.whl", hash = "sha256:e0d2f68eaa0a755edd9a47d40e50dba6df2bceda66960dee1218da81a2834d27"}, + {file = "grpcio-1.68.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:8af6137cc4ae8e421690d276e7627cfc726d4293f6607acf9ea7260bd8fc3d7d"}, + {file = "grpcio-1.68.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4028b8e9a3bff6f377698587d642e24bd221810c06579a18420a17688e421af7"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:f60fa2adf281fd73ae3a50677572521edca34ba373a45b457b5ebe87c2d01e1d"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e18589e747c1e70b60fab6767ff99b2d0c359ea1db8a2cb524477f93cdbedf5b"}, + {file = "grpcio-1.68.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e0d30f3fee9372796f54d3100b31ee70972eaadcc87314be369360248a3dcffe"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:7e0a3e72c0e9a1acab77bef14a73a416630b7fd2cbd893c0a873edc47c42c8cd"}, + {file = "grpcio-1.68.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a831dcc343440969aaa812004685ed322cdb526cd197112d0db303b0da1e8659"}, + {file = "grpcio-1.68.0-cp312-cp312-win32.whl", hash = "sha256:5a180328e92b9a0050958ced34dddcb86fec5a8b332f5a229e353dafc16cd332"}, + {file = "grpcio-1.68.0-cp312-cp312-win_amd64.whl", hash = "sha256:2bddd04a790b69f7a7385f6a112f46ea0b34c4746f361ebafe9ca0be567c78e9"}, + {file = "grpcio-1.68.0-cp313-cp313-linux_armv7l.whl", hash = "sha256:fc05759ffbd7875e0ff2bd877be1438dfe97c9312bbc558c8284a9afa1d0f40e"}, + {file = "grpcio-1.68.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:15fa1fe25d365a13bc6d52fcac0e3ee1f9baebdde2c9b3b2425f8a4979fccea1"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_aarch64.whl", hash = "sha256:32a9cb4686eb2e89d97022ecb9e1606d132f85c444354c17a7dbde4a455e4a3b"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dba037ff8d284c8e7ea9a510c8ae0f5b016004f13c3648f72411c464b67ff2fb"}, + {file = "grpcio-1.68.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0efbbd849867e0e569af09e165363ade75cf84f5229b2698d53cf22c7a4f9e21"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:4e300e6978df0b65cc2d100c54e097c10dfc7018b9bd890bbbf08022d47f766d"}, + {file = "grpcio-1.68.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:6f9c7ad1a23e1047f827385f4713b5b8c6c7d325705be1dd3e31fb00dcb2f665"}, + {file = "grpcio-1.68.0-cp313-cp313-win32.whl", hash = "sha256:3ac7f10850fd0487fcce169c3c55509101c3bde2a3b454869639df2176b60a03"}, + {file = "grpcio-1.68.0-cp313-cp313-win_amd64.whl", hash = "sha256:afbf45a62ba85a720491bfe9b2642f8761ff348006f5ef67e4622621f116b04a"}, + {file = "grpcio-1.68.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:f8f695d9576ce836eab27ba7401c60acaf9ef6cf2f70dfe5462055ba3df02cc3"}, + {file = "grpcio-1.68.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9fe1b141cda52f2ca73e17d2d3c6a9f3f3a0c255c216b50ce616e9dca7e3441d"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:4df81d78fd1646bf94ced4fb4cd0a7fe2e91608089c522ef17bc7db26e64effd"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46a2d74d4dd8993151c6cd585594c082abe74112c8e4175ddda4106f2ceb022f"}, + {file = "grpcio-1.68.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a17278d977746472698460c63abf333e1d806bd41f2224f90dbe9460101c9796"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:15377bce516b1c861c35e18eaa1c280692bf563264836cece693c0f169b48829"}, + {file = "grpcio-1.68.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cc5f0a4f5904b8c25729a0498886b797feb817d1fd3812554ffa39551112c161"}, + {file = "grpcio-1.68.0-cp38-cp38-win32.whl", hash = "sha256:def1a60a111d24376e4b753db39705adbe9483ef4ca4761f825639d884d5da78"}, + {file = "grpcio-1.68.0-cp38-cp38-win_amd64.whl", hash = "sha256:55d3b52fd41ec5772a953612db4e70ae741a6d6ed640c4c89a64f017a1ac02b5"}, + {file = "grpcio-1.68.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:0d230852ba97654453d290e98d6aa61cb48fa5fafb474fb4c4298d8721809354"}, + {file = "grpcio-1.68.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:50992f214264e207e07222703c17d9cfdcc2c46ed5a1ea86843d440148ebbe10"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:14331e5c27ed3545360464a139ed279aa09db088f6e9502e95ad4bfa852bb116"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f84890b205692ea813653ece4ac9afa2139eae136e419231b0eec7c39fdbe4c2"}, + {file = "grpcio-1.68.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0cf343c6f4f6aa44863e13ec9ddfe299e0be68f87d68e777328bff785897b05"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fd2c2d47969daa0e27eadaf15c13b5e92605c5e5953d23c06d0b5239a2f176d3"}, + {file = "grpcio-1.68.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:18668e36e7f4045820f069997834e94e8275910b1f03e078a6020bd464cb2363"}, + {file = "grpcio-1.68.0-cp39-cp39-win32.whl", hash = "sha256:2af76ab7c427aaa26aa9187c3e3c42f38d3771f91a20f99657d992afada2294a"}, + {file = "grpcio-1.68.0-cp39-cp39-win_amd64.whl", hash = "sha256:e694b5928b7b33ca2d3b4d5f9bf8b5888906f181daff6b406f4938f3a997a490"}, + {file = "grpcio-1.68.0.tar.gz", hash = "sha256:7e7483d39b4a4fddb9906671e9ea21aaad4f031cdfc349fec76bdfa1e404543a"}, +] + +[package.extras] +protobuf = ["grpcio-tools (>=1.68.0)"] [[package]] name = "grpcio-status" -version = "1.66.2" +version = "1.68.0" description = "Status proto mapping for gRPC" optional = false python-versions = ">=3.8" files = [ - {file = "grpcio_status-1.66.2-py3-none-any.whl", hash = "sha256:e5fe189f6897d12aa9cd74408a17ca41e44fad30871cf84f5cbd17bd713d2455"}, - {file = "grpcio_status-1.66.2.tar.gz", hash = "sha256:fb55cbb5c2e67062f7a4d5c99e489d074fb57e98678d5c3c6692a2d74d89e9ae"}, + {file = "grpcio_status-1.68.0-py3-none-any.whl", hash = "sha256:0a71b15d989f02df803b4ba85c5bf1f43aeaa58ac021e5f9974b8cadc41f784d"}, + {file = "grpcio_status-1.68.0.tar.gz", hash = "sha256:8369823de22ab6a2cddb3804669c149ae7a71819e127c2dca7c2322028d52bea"}, ] [package.dependencies] googleapis-common-protos = ">=1.5.5" -grpcio = ">=1.66.2" +grpcio = ">=1.68.0" protobuf = ">=5.26.1,<6.0dev" [[package]] @@ -1370,51 +1375,58 @@ pyparsing = {version = ">=2.4.2,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.0.2 || >3.0 [[package]] name = "httptools" -version = "0.6.1" +version = "0.6.4" description = "A collection of framework independent HTTP protocol utils." optional = false python-versions = ">=3.8.0" files = [ - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d2f6c3c4cb1948d912538217838f6e9960bc4a521d7f9b323b3da579cd14532f"}, - {file = "httptools-0.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:00d5d4b68a717765b1fabfd9ca755bd12bf44105eeb806c03d1962acd9b8e563"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:639dc4f381a870c9ec860ce5c45921db50205a37cc3334e756269736ff0aac58"}, - {file = "httptools-0.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e57997ac7fb7ee43140cc03664de5f268813a481dff6245e0075925adc6aa185"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0ac5a0ae3d9f4fe004318d64b8a854edd85ab76cffbf7ef5e32920faef62f142"}, - {file = "httptools-0.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3f30d3ce413088a98b9db71c60a6ada2001a08945cb42dd65a9a9fe228627658"}, - {file = "httptools-0.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1ed99a373e327f0107cb513b61820102ee4f3675656a37a50083eda05dc9541b"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7a7ea483c1a4485c71cb5f38be9db078f8b0e8b4c4dc0210f531cdd2ddac1ef1"}, - {file = "httptools-0.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:85ed077c995e942b6f1b07583e4eb0a8d324d418954fc6af913d36db7c05a5a0"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b0bb634338334385351a1600a73e558ce619af390c2b38386206ac6a27fecfc"}, - {file = "httptools-0.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d9ceb2c957320def533671fc9c715a80c47025139c8d1f3797477decbc6edd2"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4f0f8271c0a4db459f9dc807acd0eadd4839934a4b9b892f6f160e94da309837"}, - {file = "httptools-0.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6a4f5ccead6d18ec072ac0b84420e95d27c1cdf5c9f1bc8fbd8daf86bd94f43d"}, - {file = "httptools-0.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:5cceac09f164bcba55c0500a18fe3c47df29b62353198e4f37bbcc5d591172c3"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:75c8022dca7935cba14741a42744eee13ba05db00b27a4b940f0d646bd4d56d0"}, - {file = "httptools-0.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:48ed8129cd9a0d62cf4d1575fcf90fb37e3ff7d5654d3a5814eb3d55f36478c2"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f58e335a1402fb5a650e271e8c2d03cfa7cea46ae124649346d17bd30d59c90"}, - {file = "httptools-0.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93ad80d7176aa5788902f207a4e79885f0576134695dfb0fefc15b7a4648d503"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9bb68d3a085c2174c2477eb3ffe84ae9fb4fde8792edb7bcd09a1d8467e30a84"}, - {file = "httptools-0.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:b512aa728bc02354e5ac086ce76c3ce635b62f5fbc32ab7082b5e582d27867bb"}, - {file = "httptools-0.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:97662ce7fb196c785344d00d638fc9ad69e18ee4bfb4000b35a52efe5adcc949"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:8e216a038d2d52ea13fdd9b9c9c7459fb80d78302b257828285eca1c773b99b3"}, - {file = "httptools-0.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e802e0b2378ade99cd666b5bffb8b2a7cc8f3d28988685dc300469ea8dd86cb"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4bd3e488b447046e386a30f07af05f9b38d3d368d1f7b4d8f7e10af85393db97"}, - {file = "httptools-0.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe467eb086d80217b7584e61313ebadc8d187a4d95bb62031b7bab4b205c3ba3"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3c3b214ce057c54675b00108ac42bacf2ab8f85c58e3f324a4e963bbc46424f4"}, - {file = "httptools-0.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8ae5b97f690badd2ca27cbf668494ee1b6d34cf1c464271ef7bfa9ca6b83ffaf"}, - {file = "httptools-0.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:405784577ba6540fa7d6ff49e37daf104e04f4b4ff2d1ac0469eaa6a20fde084"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:95fb92dd3649f9cb139e9c56604cc2d7c7bf0fc2e7c8d7fbd58f96e35eddd2a3"}, - {file = "httptools-0.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dcbab042cc3ef272adc11220517278519adf8f53fd3056d0e68f0a6f891ba94e"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cf2372e98406efb42e93bfe10f2948e467edfd792b015f1b4ecd897903d3e8d"}, - {file = "httptools-0.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:678fcbae74477a17d103b7cae78b74800d795d702083867ce160fc202104d0da"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e0b281cf5a125c35f7f6722b65d8542d2e57331be573e9e88bc8b0115c4a7a81"}, - {file = "httptools-0.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95658c342529bba4e1d3d2b1a874db16c7cca435e8827422154c9da76ac4e13a"}, - {file = "httptools-0.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ebaec1bf683e4bf5e9fbb49b8cc36da482033596a415b3e4ebab5a4c0d7ec5e"}, - {file = "httptools-0.6.1.tar.gz", hash = "sha256:c6e26c30455600b95d94b1b836085138e82f177351454ee841c148f93a9bad5a"}, -] - -[package.extras] -test = ["Cython (>=0.29.24,<0.30.0)"] + {file = "httptools-0.6.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3c73ce323711a6ffb0d247dcd5a550b8babf0f757e86a52558fe5b86d6fefcc0"}, + {file = "httptools-0.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:345c288418f0944a6fe67be8e6afa9262b18c7626c3ef3c28adc5eabc06a68da"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deee0e3343f98ee8047e9f4c5bc7cedbf69f5734454a94c38ee829fb2d5fa3c1"}, + {file = "httptools-0.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca80b7485c76f768a3bc83ea58373f8db7b015551117375e4918e2aa77ea9b50"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:90d96a385fa941283ebd231464045187a31ad932ebfa541be8edf5b3c2328959"}, + {file = "httptools-0.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:59e724f8b332319e2875efd360e61ac07f33b492889284a3e05e6d13746876f4"}, + {file = "httptools-0.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:c26f313951f6e26147833fc923f78f95604bbec812a43e5ee37f26dc9e5a686c"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f47f8ed67cc0ff862b84a1189831d1d33c963fb3ce1ee0c65d3b0cbe7b711069"}, + {file = "httptools-0.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0614154d5454c21b6410fdf5262b4a3ddb0f53f1e1721cfd59d55f32138c578a"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8787367fbdfccae38e35abf7641dafc5310310a5987b689f4c32cc8cc3ee975"}, + {file = "httptools-0.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40b0f7fe4fd38e6a507bdb751db0379df1e99120c65fbdc8ee6c1d044897a636"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40a5ec98d3f49904b9fe36827dcf1aadfef3b89e2bd05b0e35e94f97c2b14721"}, + {file = "httptools-0.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:dacdd3d10ea1b4ca9df97a0a303cbacafc04b5cd375fa98732678151643d4988"}, + {file = "httptools-0.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:288cd628406cc53f9a541cfaf06041b4c71d751856bab45e3702191f931ccd17"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:df017d6c780287d5c80601dafa31f17bddb170232d85c066604d8558683711a2"}, + {file = "httptools-0.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:85071a1e8c2d051b507161f6c3e26155b5c790e4e28d7f236422dbacc2a9cc44"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69422b7f458c5af875922cdb5bd586cc1f1033295aa9ff63ee196a87519ac8e1"}, + {file = "httptools-0.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:16e603a3bff50db08cd578d54f07032ca1631450ceb972c2f834c2b860c28ea2"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:ec4f178901fa1834d4a060320d2f3abc5c9e39766953d038f1458cb885f47e81"}, + {file = "httptools-0.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f9eb89ecf8b290f2e293325c646a211ff1c2493222798bb80a530c5e7502494f"}, + {file = "httptools-0.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:db78cb9ca56b59b016e64b6031eda5653be0589dba2b1b43453f6e8b405a0970"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ade273d7e767d5fae13fa637f4d53b6e961fb7fd93c7797562663f0171c26660"}, + {file = "httptools-0.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:856f4bc0478ae143bad54a4242fccb1f3f86a6e1be5548fecfd4102061b3a083"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:322d20ea9cdd1fa98bd6a74b77e2ec5b818abdc3d36695ab402a0de8ef2865a3"}, + {file = "httptools-0.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d87b29bd4486c0093fc64dea80231f7c7f7eb4dc70ae394d70a495ab8436071"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:342dd6946aa6bda4b8f18c734576106b8a31f2fe31492881a9a160ec84ff4bd5"}, + {file = "httptools-0.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b36913ba52008249223042dca46e69967985fb4051951f94357ea681e1f5dc0"}, + {file = "httptools-0.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:28908df1b9bb8187393d5b5db91435ccc9c8e891657f9cbb42a2541b44c82fc8"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d3f0d369e7ffbe59c4b6116a44d6a8eb4783aae027f2c0b366cf0aa964185dba"}, + {file = "httptools-0.6.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:94978a49b8f4569ad607cd4946b759d90b285e39c0d4640c6b36ca7a3ddf2efc"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dc6a8e399e15ea525305a2ddba998b0af5caa2566bcd79dcbe8948181eeaff"}, + {file = "httptools-0.6.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab9ba8dcf59de5181f6be44a77458e45a578fc99c31510b8c65b7d5acc3cf490"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc411e1c0a7dcd2f902c7c48cf079947a7e65b5485dea9decb82b9105ca71a43"}, + {file = "httptools-0.6.4-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:d54efd20338ac52ba31e7da78e4a72570cf729fac82bc31ff9199bedf1dc7440"}, + {file = "httptools-0.6.4-cp38-cp38-win_amd64.whl", hash = "sha256:df959752a0c2748a65ab5387d08287abf6779ae9165916fe053e68ae1fbdc47f"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:85797e37e8eeaa5439d33e556662cc370e474445d5fab24dcadc65a8ffb04003"}, + {file = "httptools-0.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:db353d22843cf1028f43c3651581e4bb49374d85692a85f95f7b9a130e1b2cab"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ffd262a73d7c28424252381a5b854c19d9de5f56f075445d33919a637e3547"}, + {file = "httptools-0.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703c346571fa50d2e9856a37d7cd9435a25e7fd15e236c397bf224afaa355fe9"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:aafe0f1918ed07b67c1e838f950b1c1fabc683030477e60b335649b8020e1076"}, + {file = "httptools-0.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0e563e54979e97b6d13f1bbc05a96109923e76b901f786a5eae36e99c01237bd"}, + {file = "httptools-0.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:b799de31416ecc589ad79dd85a0b2657a8fe39327944998dea368c1d4c9e55e6"}, + {file = "httptools-0.6.4.tar.gz", hash = "sha256:4e93eee4add6493b59a5c514da98c939b244fce4a0d8879cd3f466562f4b7d5c"}, +] + +[package.extras] +test = ["Cython (>=0.29.24)"] [[package]] name = "httpx" @@ -1469,22 +1481,26 @@ all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2 [[package]] name = "importlib-metadata" -version = "8.4.0" +version = "8.5.0" description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, - {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, ] [package.dependencies] -zipp = ">=0.5" +zipp = ">=3.20" [package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] [[package]] name = "iniconfig" @@ -1644,6 +1660,45 @@ files = [ [package.dependencies] referencing = ">=0.31.0" +[[package]] +name = "launchdarkly-eventsource" +version = "1.2.0" +description = "LaunchDarkly SSE Client" +optional = false +python-versions = ">=3.8" +files = [ + {file = "launchdarkly_eventsource-1.2.0-py3-none-any.whl", hash = "sha256:9b5ec7149e2ad9995be22ad5361deb480c229701e6b0cc799e94aa14f067b77b"}, + {file = "launchdarkly_eventsource-1.2.0.tar.gz", hash = "sha256:8cb3301ec0daeb5e17eaa37b3b65f6660fab851b317e69271185ef2fb42c2fde"}, +] + +[package.dependencies] +urllib3 = ">=1.26.0,<3" + +[[package]] +name = "launchdarkly-server-sdk" +version = "9.8.0" +description = "LaunchDarkly SDK for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "launchdarkly_server_sdk-9.8.0-py3-none-any.whl", hash = "sha256:8cb72f3cd283bd3b1954d59b8197f1467b35d5c10449904aaf560d59d4ceb368"}, + {file = "launchdarkly_server_sdk-9.8.0.tar.gz", hash = "sha256:e50a5eef770a5d0c609cf823c60ad9526f2f645e67efc638af31e7582ff62050"}, +] + +[package.dependencies] +certifi = ">=2018.4.16" +expiringdict = ">=1.1.4" +launchdarkly-eventsource = ">=1.1.0,<2.0.0" +pyRFC3339 = ">=1.0" +semver = ">=2.10.2" +urllib3 = ">=1.26.0,<3" + +[package.extras] +consul = ["python-consul (>=1.0.1)"] +dynamodb = ["boto3 (>=1.9.71)"] +redis = ["redis (>=2.10.5)"] +test-filesource = ["pyyaml (>=5.3.1)", "watchdog (>=3.0.0)"] + [[package]] name = "markupsafe" version = "2.1.5" @@ -1868,27 +1923,28 @@ signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] [[package]] name = "ollama" -version = "0.3.3" +version = "0.4.1" description = "The official Python client for Ollama." optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "ollama-0.3.3-py3-none-any.whl", hash = "sha256:ca6242ce78ab34758082b7392df3f9f6c2cb1d070a9dede1a4c545c929e16dba"}, - {file = "ollama-0.3.3.tar.gz", hash = "sha256:f90a6d61803117f40b0e8ff17465cab5e1eb24758a473cfe8101aff38bc13b51"}, + {file = "ollama-0.4.1-py3-none-any.whl", hash = "sha256:b6fb16aa5a3652633e1716acb12cf2f44aa18beb229329e46a0302734822dfad"}, + {file = "ollama-0.4.1.tar.gz", hash = "sha256:8c6b5e7ff80dd0b8692150b03359f60bac7ca162b088c604069409142a684ad3"}, ] [package.dependencies] httpx = ">=0.27.0,<0.28.0" +pydantic = ">=2.9.0,<3.0.0" [[package]] name = "openai" -version = "1.54.3" +version = "1.55.1" description = "The official Python library for the openai API" optional = false python-versions = ">=3.8" files = [ - {file = "openai-1.54.3-py3-none-any.whl", hash = "sha256:f18dbaf09c50d70c4185b892a2a553f80681d1d866323a2da7f7be2f688615d5"}, - {file = "openai-1.54.3.tar.gz", hash = "sha256:7511b74eeb894ac0b0253dc71f087a15d2e4d71d22d0088767205143d880cca6"}, + {file = "openai-1.55.1-py3-none-any.whl", hash = "sha256:d10d96a4f9dc5f05d38dea389119ec8dcd24bc9698293c8357253c601b4a77a5"}, + {file = "openai-1.55.1.tar.gz", hash = "sha256:471324321e7739214f16a544e801947a046d3c5d516fae8719a317234e4968d3"}, ] [package.dependencies] @@ -1906,18 +1962,18 @@ datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] [[package]] name = "opentelemetry-api" -version = "1.27.0" +version = "1.28.2" description = "OpenTelemetry Python API" optional = false python-versions = ">=3.8" files = [ - {file = "opentelemetry_api-1.27.0-py3-none-any.whl", hash = "sha256:953d5871815e7c30c81b56d910c707588000fff7a3ca1c73e6531911d53065e7"}, - {file = "opentelemetry_api-1.27.0.tar.gz", hash = "sha256:ed673583eaa5f81b5ce5e86ef7cdaf622f88ef65f0b9aab40b843dcae5bef342"}, + {file = "opentelemetry_api-1.28.2-py3-none-any.whl", hash = "sha256:6fcec89e265beb258fe6b1acaaa3c8c705a934bd977b9f534a2b7c0d2d4275a6"}, + {file = "opentelemetry_api-1.28.2.tar.gz", hash = "sha256:ecdc70c7139f17f9b0cf3742d57d7020e3e8315d6cffcdf1a12a905d45b19cc0"}, ] [package.dependencies] deprecated = ">=1.2.6" -importlib-metadata = ">=6.0,<=8.4.0" +importlib-metadata = ">=6.0,<=8.5.0" [[package]] name = "packaging" @@ -2051,13 +2107,13 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "poethepoet" -version = "0.30.0" +version = "0.31.0" description = "A task runner that works well with poetry." optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "poethepoet-0.30.0-py3-none-any.whl", hash = "sha256:bf875741407a98da9e96f2f2d0b2c4c34f56d89939a7f53a4b6b3a64b546ec4e"}, - {file = "poethepoet-0.30.0.tar.gz", hash = "sha256:9f7ccda2d6525616ce989ca8ef973739fd668f50bef0b9d3631421d504d9ae4a"}, + {file = "poethepoet-0.31.0-py3-none-any.whl", hash = "sha256:5067c5adf9f228b8af1f3df7d57dc319ed8b3f153bf21faf99f7b74494174c3d"}, + {file = "poethepoet-0.31.0.tar.gz", hash = "sha256:b1cffb120149101b02ffa0583c6e61dfee53953a741df3dabf179836bdef97f5"}, ] [package.dependencies] @@ -2156,13 +2212,13 @@ node = ["nodejs-bin"] [[package]] name = "proto-plus" -version = "1.24.0" +version = "1.25.0" description = "Beautiful, Pythonic protocol buffers." optional = false python-versions = ">=3.7" files = [ - {file = "proto-plus-1.24.0.tar.gz", hash = "sha256:30b72a5ecafe4406b0d339db35b56c4059064e69227b8c3bda7462397f966445"}, - {file = "proto_plus-1.24.0-py3-none-any.whl", hash = "sha256:402576830425e5f6ce4c2a6702400ac79897dab0b4343821aa5188b0fab81a12"}, + {file = "proto_plus-1.25.0-py3-none-any.whl", hash = "sha256:c91fc4a65074ade8e458e95ef8bac34d4008daa7cce4a12d6707066fca648961"}, + {file = "proto_plus-1.25.0.tar.gz", hash = "sha256:fbb17f57f7bd05a68b7707e745e26528b0b3c34e378db91eef93912c54982d91"}, ] [package.dependencies] @@ -2530,6 +2586,17 @@ files = [ [package.extras] diagrams = ["jinja2", "railroad-diagrams"] +[[package]] +name = "pyrfc3339" +version = "2.0.1" +description = "Generate and parse RFC 3339 timestamps" +optional = false +python-versions = "*" +files = [ + {file = "pyRFC3339-2.0.1-py3-none-any.whl", hash = "sha256:30b70a366acac3df7386b558c21af871522560ed7f3f73cf344b8c2cbb8b0c9d"}, + {file = "pyrfc3339-2.0.1.tar.gz", hash = "sha256:e47843379ea35c1296c3b6c67a948a1a490ae0584edfcbdea0eaffb5dd29960b"}, +] + [[package]] name = "pyright" version = "1.1.389" @@ -2604,6 +2671,23 @@ pytest = ">=8.2,<9" docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"] testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"] +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-watcher" version = "0.4.3" @@ -2772,20 +2856,20 @@ rpds-py = ">=0.7.0" [[package]] name = "replicate" -version = "1.0.3" +version = "1.0.4" description = "Python client for Replicate" optional = false python-versions = ">=3.8" files = [ - {file = "replicate-1.0.3-py3-none-any.whl", hash = "sha256:8c49d63444b7ea9ac1d6af99eb23a01efb5b7f079cc8a020d6f52b38843db1da"}, - {file = "replicate-1.0.3.tar.gz", hash = "sha256:0fd9ca5230fe67c42e4508dd96a5b1414b3fefa5342f8921dbb63c74266cb130"}, + {file = "replicate-1.0.4-py3-none-any.whl", hash = "sha256:f568f6271ff715067901b6094c23c37373bbcfd7de0ff9b85e9c9ead567e09e7"}, + {file = "replicate-1.0.4.tar.gz", hash = "sha256:f718601863ef1f419aa7dcdab1ea8770ba5489b571b86edf840cd506d68758ef"}, ] [package.dependencies] httpx = ">=0.21.0,<1" packaging = "*" pydantic = ">1.10.7" -typing-extensions = ">=4.5.0" +typing_extensions = ">=4.5.0" [[package]] name = "requests" @@ -2954,40 +3038,51 @@ pyasn1 = ">=0.1.3" [[package]] name = "ruff" -version = "0.7.4" +version = "0.8.0" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.7.4-py3-none-linux_armv6l.whl", hash = "sha256:a4919925e7684a3f18e18243cd6bea7cfb8e968a6eaa8437971f681b7ec51478"}, - {file = "ruff-0.7.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:cfb365c135b830778dda8c04fb7d4280ed0b984e1aec27f574445231e20d6c63"}, - {file = "ruff-0.7.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:63a569b36bc66fbadec5beaa539dd81e0527cb258b94e29e0531ce41bacc1f20"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d06218747d361d06fd2fdac734e7fa92df36df93035db3dc2ad7aa9852cb109"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e0cea28d0944f74ebc33e9f934238f15c758841f9f5edd180b5315c203293452"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:80094ecd4793c68b2571b128f91754d60f692d64bc0d7272ec9197fdd09bf9ea"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:997512325c6620d1c4c2b15db49ef59543ef9cd0f4aa8065ec2ae5103cedc7e7"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:00b4cf3a6b5fad6d1a66e7574d78956bbd09abfd6c8a997798f01f5da3d46a05"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7dbdc7d8274e1422722933d1edddfdc65b4336abf0b16dfcb9dedd6e6a517d06"}, - {file = "ruff-0.7.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e92dfb5f00eaedb1501b2f906ccabfd67b2355bdf117fea9719fc99ac2145bc"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:3bd726099f277d735dc38900b6a8d6cf070f80828877941983a57bca1cd92172"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:2e32829c429dd081ee5ba39aef436603e5b22335c3d3fff013cd585806a6486a"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:662a63b4971807623f6f90c1fb664613f67cc182dc4d991471c23c541fee62dd"}, - {file = "ruff-0.7.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:876f5e09eaae3eb76814c1d3b68879891d6fde4824c015d48e7a7da4cf066a3a"}, - {file = "ruff-0.7.4-py3-none-win32.whl", hash = "sha256:75c53f54904be42dd52a548728a5b572344b50d9b2873d13a3f8c5e3b91f5cac"}, - {file = "ruff-0.7.4-py3-none-win_amd64.whl", hash = "sha256:745775c7b39f914238ed1f1b0bebed0b9155a17cd8bc0b08d3c87e4703b990d6"}, - {file = "ruff-0.7.4-py3-none-win_arm64.whl", hash = "sha256:11bff065102c3ae9d3ea4dc9ecdfe5a5171349cdd0787c1fc64761212fc9cf1f"}, - {file = "ruff-0.7.4.tar.gz", hash = "sha256:cd12e35031f5af6b9b93715d8c4f40360070b2041f81273d0527683d5708fce2"}, + {file = "ruff-0.8.0-py3-none-linux_armv6l.whl", hash = "sha256:fcb1bf2cc6706adae9d79c8d86478677e3bbd4ced796ccad106fd4776d395fea"}, + {file = "ruff-0.8.0-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:295bb4c02d58ff2ef4378a1870c20af30723013f441c9d1637a008baaf928c8b"}, + {file = "ruff-0.8.0-py3-none-macosx_11_0_arm64.whl", hash = "sha256:7b1f1c76b47c18fa92ee78b60d2d20d7e866c55ee603e7d19c1e991fad933a9a"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb0d4f250a7711b67ad513fde67e8870109e5ce590a801c3722580fe98c33a99"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0e55cce9aa93c5d0d4e3937e47b169035c7e91c8655b0974e61bb79cf398d49c"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f4cd64916d8e732ce6b87f3f5296a8942d285bbbc161acee7fe561134af64f9"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:c5c1466be2a2ebdf7c5450dd5d980cc87c8ba6976fb82582fea18823da6fa362"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dabfd05b96b7b8f2da00d53c514eea842bff83e41e1cceb08ae1966254a51df"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:facebdfe5a5af6b1588a1d26d170635ead6892d0e314477e80256ef4a8470cf3"}, + {file = "ruff-0.8.0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87a8e86bae0dbd749c815211ca11e3a7bd559b9710746c559ed63106d382bd9c"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:85e654f0ded7befe2d61eeaf3d3b1e4ef3894469cd664ffa85006c7720f1e4a2"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:83a55679c4cb449fa527b8497cadf54f076603cc36779b2170b24f704171ce70"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_i686.whl", hash = "sha256:812e2052121634cf13cd6fddf0c1871d0ead1aad40a1a258753c04c18bb71bbd"}, + {file = "ruff-0.8.0-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:780d5d8523c04202184405e60c98d7595bdb498c3c6abba3b6d4cdf2ca2af426"}, + {file = "ruff-0.8.0-py3-none-win32.whl", hash = "sha256:5fdb6efecc3eb60bba5819679466471fd7d13c53487df7248d6e27146e985468"}, + {file = "ruff-0.8.0-py3-none-win_amd64.whl", hash = "sha256:582891c57b96228d146725975fbb942e1f30a0c4ba19722e692ca3eb25cc9b4f"}, + {file = "ruff-0.8.0-py3-none-win_arm64.whl", hash = "sha256:ba93e6294e9a737cd726b74b09a6972e36bb511f9a102f1d9a7e1ce94dd206a6"}, + {file = "ruff-0.8.0.tar.gz", hash = "sha256:a7ccfe6331bf8c8dad715753e157457faf7351c2b69f62f32c165c2dbcbacd44"}, +] + +[[package]] +name = "semver" +version = "3.0.2" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "semver-3.0.2-py3-none-any.whl", hash = "sha256:b1ea4686fe70b981f85359eda33199d60c53964284e0cfb4977d243e37cf4bf4"}, + {file = "semver-3.0.2.tar.gz", hash = "sha256:6253adb39c70f6e51afed2fa7152bcd414c411286088fb4b9effb133885ab4cc"}, ] [[package]] name = "sentry-sdk" -version = "2.18.0" +version = "2.19.0" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = ">=3.6" files = [ - {file = "sentry_sdk-2.18.0-py2.py3-none-any.whl", hash = "sha256:ee70e27d1bbe4cd52a38e1bd28a5fadb9b17bc29d91b5f2b97ae29c0a7610442"}, - {file = "sentry_sdk-2.18.0.tar.gz", hash = "sha256:0dc21febd1ab35c648391c664df96f5f79fb0d92d7d4225cd9832e53a617cafd"}, + {file = "sentry_sdk-2.19.0-py2.py3-none-any.whl", hash = "sha256:7b0b3b709dee051337244a09a30dbf6e95afe0d34a1f8b430d45e0982a7c125b"}, + {file = "sentry_sdk-2.19.0.tar.gz", hash = "sha256:ee4a4d2ae8bfe3cac012dcf3e4607975904c137e1738116549fc3dbbb6ff0e36"}, ] [package.dependencies] @@ -3013,7 +3108,7 @@ grpcio = ["grpcio (>=1.21.1)", "protobuf (>=3.8.0)"] http2 = ["httpcore[http2] (==1.*)"] httpx = ["httpx (>=0.16.0)"] huey = ["huey (>=2)"] -huggingface-hub = ["huggingface-hub (>=0.22)"] +huggingface-hub = ["huggingface_hub (>=0.22)"] langchain = ["langchain (>=0.0.210)"] launchdarkly = ["launchdarkly-server-sdk (>=9.8.0)"] litestar = ["litestar (>=2.0.0)"] @@ -3022,7 +3117,7 @@ openai = ["openai (>=1.0.0)", "tiktoken (>=0.3.0)"] openfeature = ["openfeature-sdk (>=0.7.1)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro"] -pure-eval = ["asttokens", "executing", "pure-eval"] +pure-eval = ["asttokens", "executing", "pure_eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -3397,20 +3492,20 @@ zstd = ["zstandard (>=0.18.0)"] [[package]] name = "uvicorn" -version = "0.32.0" +version = "0.32.1" description = "The lightning-fast ASGI server." optional = false python-versions = ">=3.8" files = [ - {file = "uvicorn-0.32.0-py3-none-any.whl", hash = "sha256:60b8f3a5ac027dcd31448f411ced12b5ef452c646f76f02f8cc3f25d8d26fd82"}, - {file = "uvicorn-0.32.0.tar.gz", hash = "sha256:f78b36b143c16f54ccdb8190d0a26b5f1901fe5a3c777e1ab29f26391af8551e"}, + {file = "uvicorn-0.32.1-py3-none-any.whl", hash = "sha256:82ad92fd58da0d12af7482ecdb5f2470a04c9c9a53ced65b9bbb4a205377602e"}, + {file = "uvicorn-0.32.1.tar.gz", hash = "sha256:ee9519c246a72b1c084cea8d3b44ed6026e78a4a309cbedae9c37e4cb9fbb175"}, ] [package.dependencies] click = ">=7.0" colorama = {version = ">=0.4", optional = true, markers = "sys_platform == \"win32\" and extra == \"standard\""} h11 = ">=0.8" -httptools = {version = ">=0.5.0", optional = true, markers = "extra == \"standard\""} +httptools = {version = ">=0.6.3", optional = true, markers = "extra == \"standard\""} python-dotenv = {version = ">=0.13", optional = true, markers = "extra == \"standard\""} pyyaml = {version = ">=5.1", optional = true, markers = "extra == \"standard\""} typing-extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} @@ -3419,7 +3514,7 @@ watchfiles = {version = ">=0.13", optional = true, markers = "extra == \"standar websockets = {version = ">=10.4", optional = true, markers = "extra == \"standard\""} [package.extras] -standard = ["colorama (>=0.4)", "httptools (>=0.5.0)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] +standard = ["colorama (>=0.4)", "httptools (>=0.6.3)", "python-dotenv (>=0.13)", "pyyaml (>=5.1)", "uvloop (>=0.14.0,!=0.15.0,!=0.15.1)", "watchfiles (>=0.13)", "websockets (>=10.4)"] [[package]] name = "uvloop" @@ -3913,13 +4008,13 @@ requests = "*" [[package]] name = "zipp" -version = "3.20.2" +version = "3.21.0" description = "Backport of pathlib-compatible object wrapper for zip files" optional = false -python-versions = ">=3.8" +python-versions = ">=3.9" files = [ - {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, - {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, + {file = "zipp-3.21.0-py3-none-any.whl", hash = "sha256:ac1bbe05fd2991f160ebce24ffbac5f6d11d83dc90891255885223d42b3cd931"}, + {file = "zipp-3.21.0.tar.gz", hash = "sha256:2c9958f6430a2040341a52eb608ed6dd93ef4392e02ffe219417c1b28b5dd1f4"}, ] [package.extras] @@ -3933,4 +4028,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "02293c7c8933c2a6f7bb62af70212a7ca3340e6817352d743a9950874e6a9485" +content-hash = "5bc61641d782791b76f39b18625560a6652b02f3ad788f110e0258293032c34a" diff --git a/autogpt_platform/backend/pyproject.toml b/autogpt_platform/backend/pyproject.toml index 9679c4c9bc8c..2fc7d7f5269e 100644 --- a/autogpt_platform/backend/pyproject.toml +++ b/autogpt_platform/backend/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "autogpt-platform-backend" -version = "0.3.3" +version = "0.3.4" description = "A platform for building AI-powered agentic workflows" authors = ["AutoGPT "] readme = "README.md" @@ -9,53 +9,54 @@ packages = [{ include = "backend" }] [tool.poetry.dependencies] python = "^3.10" -aio-pika = "^9.4.3" +aio-pika = "^9.5.0" anthropic = "^0.39.0" -apscheduler = "^3.10.4" +apscheduler = "^3.11.0" autogpt-libs = { path = "../autogpt_libs", develop = true } click = "^8.1.7" croniter = "^5.0.1" discord-py = "^2.4.0" -fastapi = "^0.115.4" +fastapi = "^0.115.5" feedparser = "^6.0.11" flake8 = "^7.0.0" -google-api-python-client = "^2.151.0" +google-api-python-client = "^2.154.0" google-auth-oauthlib = "^1.2.1" -groq = "^0.11.0" +groq = "^0.12.0" jinja2 = "^3.1.4" jsonref = "^1.1.0" jsonschema = "^4.22.0" -ollama = "^0.3.0" -openai = "^1.54.3" +ollama = "^0.4.1" +openai = "^1.55.1" praw = "~7.8.1" prisma = "^0.15.0" psutil = "^6.1.0" -pydantic = "^2.7.2" +pydantic = "^2.9.2" pydantic-settings = "^2.3.4" pyro5 = "^5.15" pytest = "^8.2.1" pytest-asyncio = "^0.24.0" python-dotenv = "^1.0.1" redis = "^5.2.0" -sentry-sdk = "2.18.0" +sentry-sdk = "2.19.0" strenum = "^0.4.9" supabase = "^2.10.0" tenacity = "^9.0.0" -uvicorn = { extras = ["standard"], version = "^0.32.0" } +uvicorn = { extras = ["standard"], version = "^0.32.1" } websockets = "^13.1" youtube-transcript-api = "^0.6.2" googlemaps = "^4.10.0" -replicate = "^1.0.3" +replicate = "^1.0.4" pinecone = "^5.3.1" cryptography = "^43.0.3" sqlalchemy = "^2.0.36" psycopg2-binary = "^2.9.10" +launchdarkly-server-sdk = "^9.8.0" [tool.poetry.group.dev.dependencies] -poethepoet = "^0.30.0" +poethepoet = "^0.31.0" httpx = "^0.27.0" pytest-watcher = "^0.4.2" requests = "^2.32.3" -ruff = "^0.7.4" +ruff = "^0.8.0" pyright = "^1.1.389" isort = "^5.13.2" black = "^24.10.0" diff --git a/autogpt_platform/backend/test/data/test_credit.py b/autogpt_platform/backend/test/data/test_credit.py index fe8fbde81c5b..37236e3467ee 100644 --- a/autogpt_platform/backend/test/data/test_credit.py +++ b/autogpt_platform/backend/test/data/test_credit.py @@ -6,6 +6,7 @@ from backend.blocks.llm import AITextGeneratorBlock from backend.data.credit import UserCredit from backend.data.user import DEFAULT_USER_ID +from backend.integrations.credentials_store import openai_credentials from backend.util.test import SpinTestServer REFILL_VALUE = 1000 @@ -20,7 +21,14 @@ async def test_block_credit_usage(server: SpinTestServer): DEFAULT_USER_ID, current_credit, AITextGeneratorBlock().id, - {"model": "gpt-4-turbo"}, + { + "model": "gpt-4-turbo", + "credentials": { + "id": openai_credentials.id, + "provider": openai_credentials.provider, + "type": openai_credentials.type, + }, + }, 0.0, 0.0, validate_balance=False, diff --git a/autogpt_platform/backend/test/util/decorator.py b/autogpt_platform/backend/test/util/test_decorator.py similarity index 100% rename from autogpt_platform/backend/test/util/decorator.py rename to autogpt_platform/backend/test/util/test_decorator.py diff --git a/autogpt_platform/backend/test/util/test_retry.py b/autogpt_platform/backend/test/util/test_retry.py new file mode 100644 index 000000000000..d3192f4f9fac --- /dev/null +++ b/autogpt_platform/backend/test/util/test_retry.py @@ -0,0 +1,49 @@ +import asyncio + +import pytest + +from backend.util.retry import conn_retry + + +def test_conn_retry_sync_function(): + retry_count = 0 + + @conn_retry("Test", "Test function", max_retry=2, max_wait=0.1, min_wait=0.1) + def test_function(): + nonlocal retry_count + retry_count -= 1 + if retry_count > 0: + raise ValueError("Test error") + return "Success" + + retry_count = 2 + res = test_function() + assert res == "Success" + + retry_count = 100 + with pytest.raises(ValueError) as e: + test_function() + assert str(e.value) == "Test error" + + +@pytest.mark.asyncio +async def test_conn_retry_async_function(): + retry_count = 0 + + @conn_retry("Test", "Test function", max_retry=2, max_wait=0.1, min_wait=0.1) + async def test_function(): + nonlocal retry_count + await asyncio.sleep(1) + retry_count -= 1 + if retry_count > 0: + raise ValueError("Test error") + return "Success" + + retry_count = 2 + res = await test_function() + assert res == "Success" + + retry_count = 100 + with pytest.raises(ValueError) as e: + await test_function() + assert str(e.value) == "Test error" diff --git a/autogpt_platform/docker-compose.platform.yml b/autogpt_platform/docker-compose.platform.yml index 5d00f59f0241..a09fed4c8b15 100644 --- a/autogpt_platform/docker-compose.platform.yml +++ b/autogpt_platform/docker-compose.platform.yml @@ -67,7 +67,6 @@ services: - PYRO_HOST=0.0.0.0 - EXECUTIONSCHEDULER_HOST=rest_server - EXECUTIONMANAGER_HOST=executor - - DATABASEMANAGER_HOST=executor - FRONTEND_BASE_URL=http://localhost:3000 - BACKEND_CORS_ALLOW_ORIGINS=["http://localhost:3000"] - ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!! @@ -106,8 +105,6 @@ services: - ENABLE_AUTH=true - PYRO_HOST=0.0.0.0 - AGENTSERVER_HOST=rest_server - - DATABASEMANAGER_HOST=0.0.0.0 - - EXECUTIONMANAGER_HOST=0.0.0.0 - ENCRYPTION_KEY=dvziYgz0KSK8FENhju0ZYi8-fRTfAdlz6YLhdB_jhNw= # DO NOT USE IN PRODUCTION!! ports: - "8002:8000" diff --git a/autogpt_platform/frontend/package.json b/autogpt_platform/frontend/package.json index 84dbfaf35da0..f4437b93c26c 100644 --- a/autogpt_platform/frontend/package.json +++ b/autogpt_platform/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.3.3", + "version": "0.3.4", "private": true, "scripts": { "dev": "next dev", @@ -53,7 +53,7 @@ "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "cmdk": "1.0.4", - "cookie": "1.0.1", + "cookie": "1.0.2", "date-fns": "^4.1.0", "dotenv": "^16.4.5", "elliptic": "6.6.1", @@ -62,44 +62,44 @@ "next": "^14.2.13", "next-themes": "^0.4.3", "react": "^18", - "react-day-picker": "^9.3.2", + "react-day-picker": "^9.4.0", "react-dom": "^18", "react-hook-form": "^7.53.2", "react-icons": "^5.3.0", "react-markdown": "^9.0.1", "react-modal": "^3.16.1", - "react-shepherd": "^6.1.4", + "react-shepherd": "^6.1.6", "recharts": "^2.13.3", - "tailwind-merge": "^2.5.4", + "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "uuid": "^11.0.3", "zod": "^3.23.8" }, "devDependencies": { "@chromatic-com/storybook": "^3.2.2", - "@playwright/test": "^1.48.2", - "@storybook/addon-essentials": "^8.4.2", - "@storybook/addon-interactions": "^8.4.2", - "@storybook/addon-links": "^8.4.2", - "@storybook/addon-onboarding": "^8.4.2", - "@storybook/blocks": "^8.4.2", - "@storybook/nextjs": "^8.4.2", + "@playwright/test": "^1.49.0", + "@storybook/addon-essentials": "^8.4.5", + "@storybook/addon-interactions": "^8.4.5", + "@storybook/addon-links": "^8.4.5", + "@storybook/addon-onboarding": "^8.4.5", + "@storybook/blocks": "^8.4.5", + "@storybook/nextjs": "^8.4.5", "@storybook/react": "^8.3.5", "@storybook/test": "^8.3.5", "@storybook/test-runner": "^0.19.1", - "@types/node": "^22.9.0", + "@types/node": "^22.9.3", "@types/react": "^18", "@types/react-dom": "^18", "@types/react-modal": "^3.16.3", "concurrently": "^9.1.0", "eslint": "^8", "eslint-config-next": "15.0.3", - "eslint-plugin-storybook": "^0.11.0", + "eslint-plugin-storybook": "^0.11.1", "postcss": "^8", "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.8", - "storybook": "^8.4.2", - "tailwindcss": "^3.4.14", + "prettier-plugin-tailwindcss": "^0.6.9", + "storybook": "^8.4.5", + "tailwindcss": "^3.4.15", "typescript": "^5" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" diff --git a/autogpt_platform/frontend/src/app/profile/page.tsx b/autogpt_platform/frontend/src/app/profile/page.tsx index 93d9b77085cf..c1df9e705f2e 100644 --- a/autogpt_platform/frontend/src/app/profile/page.tsx +++ b/autogpt_platform/frontend/src/app/profile/page.tsx @@ -143,7 +143,9 @@ export default function PrivatePage() { return (
-

Hello {user.email}

+

+ Hello {user.email} +