From 2a8b5087178026f880fa50476616c8992cc6d640 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:12:06 +0000 Subject: [PATCH 1/2] chore: sync repo --- .github/workflows/publish-pypi.yml | 7 +- .github/workflows/release-doctor.yml | 4 +- .release-please-manifest.json | 2 +- .stats.yml | 2 +- CHANGELOG.md | 11 - CONTRIBUTING.md | 4 +- LICENSE | 2 +- README.md | 130 ++++--- SECURITY.md | 2 +- api.md | 124 +++++-- bin/check-release-environment | 4 + bin/publish-pypi | 6 +- pyproject.toml | 12 +- src/everos/__init__.py | 22 +- src/everos/_base_client.py | 34 +- src/everos/_client.py | 194 +++------- src/everos/_exceptions.py | 4 +- src/everos/_models.py | 6 - src/everos/_resource.py | 10 +- src/everos/_response.py | 4 +- src/everos/_streaming.py | 6 +- src/everos/_types.py | 3 +- src/everos/_utils/_logs.py | 2 +- src/everos/_version.py | 2 +- src/everos/lib/__init__.py | 12 + src/everos/lib/_detect.py | 116 ++++++ src/everos/lib/_errors.py | 18 + src/everos/lib/_files.py | 208 +++++++++++ src/everos/lib/_multimodal.py | 348 +++++++++++++++++ src/everos/lib/_upload.py | 258 +++++++++++++ src/everos/resources/v1/__init__.py | 38 +- src/everos/resources/v1/groups.py | 202 +++++----- src/everos/resources/v1/memories/__init__.py | 12 +- src/everos/resources/v1/memories/agent.py | 39 +- src/everos/resources/v1/memories/group.py | 39 +- src/everos/resources/v1/memories/memories.py | 299 +++++++-------- src/everos/resources/v1/object.py | 53 ++- src/everos/resources/v1/senders.py | 166 ++++----- src/everos/resources/v1/settings.py | 13 +- src/everos/resources/v1/tasks.py | 174 +++++++++ src/everos/resources/v1/v1.py | 246 +++++------- src/everos/types/__init__.py | 2 - src/everos/types/v1/__init__.py | 35 +- src/everos/types/v1/add_response.py | 20 +- src/everos/types/v1/add_result.py | 22 ++ src/everos/types/v1/episode_item.py | 51 +++ src/everos/types/v1/flush_response.py | 17 +- src/everos/types/v1/flush_result.py | 19 + src/everos/types/v1/get_mem_response.py | 33 ++ src/everos/types/v1/get_memories_response.py | 13 + .../types/v1/get_task_status_response.py | 10 + src/everos/types/v1/group_api_response.py | 22 +- ...pdate_params.py => group_create_params.py} | 4 +- ...update_params.py => group_patch_params.py} | 4 +- src/everos/types/v1/group_response.py | 24 ++ .../types/v1/llm_custom_setting_param.py | 21 ++ src/everos/types/v1/memories/__init__.py | 10 +- .../types/v1/memories/agent_add_params.py | 28 ++ .../types/v1/memories/agent_case_item.py | 42 +++ ..._params.py => agent_message_item_param.py} | 45 +-- .../types/v1/memories/agent_skill_item.py | 41 ++ .../types/v1/memories/group_add_params.py | 28 ++ ..._params.py => group_message_item_param.py} | 24 +- .../v1/memories/tool_call_function_param.py | 15 + .../types/v1/memories/tool_call_param.py | 21 ++ src/everos/types/v1/memory_add_params.py | 28 ++ src/everos/types/v1/memory_get_response.py | 168 --------- ...create_params.py => message_item_param.py} | 22 +- .../v1/object_get_presigned_url_response.py | 80 ---- src/everos/types/v1/object_sign_item.py | 31 ++ ...s.py => object_sign_item_request_param.py} | 10 +- src/everos/types/v1/object_sign_params.py | 16 + src/everos/types/v1/object_sign_response.py | 38 ++ src/everos/types/v1/object_signed_info.py | 23 ++ src/everos/types/v1/profile_item.py | 29 ++ src/everos/types/v1/raw_message_dto.py | 43 +++ .../types/v1/search_memories_response.py | 13 + ...se.py => search_memories_response_data.py} | 92 ++--- src/everos/types/v1/sender_api_response.py | 19 +- ...date_params.py => sender_create_params.py} | 4 +- ...pdate_params.py => sender_patch_params.py} | 4 +- src/everos/types/v1/sender_response.py | 21 ++ src/everos/types/v1/setting_update_params.py | 21 +- src/everos/types/v1/settings_api_response.py | 30 +- src/everos/types/v1/settings_response.py | 30 ++ .../task_status_result.py} | 10 +- tests/api_resources/v1/memories/test_agent.py | 50 +-- tests/api_resources/v1/memories/test_group.py | 46 +-- tests/api_resources/v1/test_groups.py | 178 ++++----- tests/api_resources/v1/test_memories.py | 278 +++++++------- tests/api_resources/v1/test_object.py | 40 +- tests/api_resources/v1/test_senders.py | 176 ++++----- tests/api_resources/v1/test_settings.py | 30 +- .../{test_v1.py => v1/test_tasks.py} | 60 +-- tests/conftest.py | 10 +- tests/lib/__init__.py | 0 tests/lib/conftest.py | 9 + tests/lib/test_detect.py | 197 ++++++++++ tests/lib/test_files.py | 278 ++++++++++++++ tests/lib/test_multimodal.py | 350 ++++++++++++++++++ tests/lib/test_upload.py | 333 +++++++++++++++++ tests/test_client.py | 276 +++++++------- tests/test_response.py | 26 +- tests/test_streaming.py | 30 +- 104 files changed, 4420 insertions(+), 2068 deletions(-) delete mode 100644 CHANGELOG.md create mode 100644 src/everos/lib/__init__.py create mode 100644 src/everos/lib/_detect.py create mode 100644 src/everos/lib/_errors.py create mode 100644 src/everos/lib/_files.py create mode 100644 src/everos/lib/_multimodal.py create mode 100644 src/everos/lib/_upload.py create mode 100644 src/everos/resources/v1/tasks.py create mode 100644 src/everos/types/v1/add_result.py create mode 100644 src/everos/types/v1/episode_item.py create mode 100644 src/everos/types/v1/flush_result.py create mode 100644 src/everos/types/v1/get_mem_response.py create mode 100644 src/everos/types/v1/get_memories_response.py create mode 100644 src/everos/types/v1/get_task_status_response.py rename src/everos/types/v1/{group_create_or_update_params.py => group_create_params.py} (79%) rename src/everos/types/v1/{group_update_params.py => group_patch_params.py} (79%) create mode 100644 src/everos/types/v1/group_response.py create mode 100644 src/everos/types/v1/llm_custom_setting_param.py create mode 100644 src/everos/types/v1/memories/agent_add_params.py create mode 100644 src/everos/types/v1/memories/agent_case_item.py rename src/everos/types/v1/memories/{agent_create_params.py => agent_message_item_param.py} (55%) create mode 100644 src/everos/types/v1/memories/agent_skill_item.py create mode 100644 src/everos/types/v1/memories/group_add_params.py rename src/everos/types/v1/memories/{group_create_params.py => group_message_item_param.py} (58%) create mode 100644 src/everos/types/v1/memories/tool_call_function_param.py create mode 100644 src/everos/types/v1/memories/tool_call_param.py create mode 100644 src/everos/types/v1/memory_add_params.py delete mode 100644 src/everos/types/v1/memory_get_response.py rename src/everos/types/v1/{memory_create_params.py => message_item_param.py} (61%) delete mode 100644 src/everos/types/v1/object_get_presigned_url_response.py create mode 100644 src/everos/types/v1/object_sign_item.py rename src/everos/types/v1/{object_get_presigned_url_params.py => object_sign_item_request_param.py} (67%) create mode 100644 src/everos/types/v1/object_sign_params.py create mode 100644 src/everos/types/v1/object_sign_response.py create mode 100644 src/everos/types/v1/object_signed_info.py create mode 100644 src/everos/types/v1/profile_item.py create mode 100644 src/everos/types/v1/raw_message_dto.py create mode 100644 src/everos/types/v1/search_memories_response.py rename src/everos/types/v1/{memory_search_response.py => search_memories_response_data.py} (73%) rename src/everos/types/v1/{sender_create_or_update_params.py => sender_create_params.py} (76%) rename src/everos/types/v1/{sender_update_params.py => sender_patch_params.py} (75%) create mode 100644 src/everos/types/v1/sender_response.py create mode 100644 src/everos/types/v1/settings_response.py rename src/everos/types/{v1_query_task_status_response.py => v1/task_status_result.py} (61%) rename tests/api_resources/{test_v1.py => v1/test_tasks.py} (54%) create mode 100644 tests/lib/__init__.py create mode 100644 tests/lib/conftest.py create mode 100644 tests/lib/test_detect.py create mode 100644 tests/lib/test_files.py create mode 100644 tests/lib/test_multimodal.py create mode 100644 tests/lib/test_upload.py diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index 36835e1..c5ef6c0 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,6 +1,6 @@ # This workflow is triggered when a GitHub release is created. # It can also be run manually to re-publish to PyPI in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/evermemos/everos-python/actions/workflows/publish-pypi.yml +# You can run this workflow by navigating to https://www.github.com/everos/everos-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: @@ -12,9 +12,6 @@ jobs: publish: name: publish runs-on: ubuntu-latest - permissions: - contents: read - id-token: write steps: - uses: actions/checkout@v6 @@ -27,3 +24,5 @@ jobs: - name: Publish to PyPI run: | bash ./bin/publish-pypi + env: + PYPI_TOKEN: ${{ secrets.EVER_OS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index ed8b59c..13c4770 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -9,7 +9,7 @@ jobs: release_doctor: name: release doctor runs-on: ubuntu-latest - if: github.repository == 'evermemos/everos-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + if: github.repository == 'everos/everos-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - uses: actions/checkout@v6 @@ -17,3 +17,5 @@ jobs: - name: Check release environment run: | bash ./bin/check-release-environment + env: + PYPI_TOKEN: ${{ secrets.EVER_OS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c7159c1..1332969 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.0.2" + ".": "0.0.1" } \ No newline at end of file diff --git a/.stats.yml b/.stats.yml index 4dc183b..c970a94 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 19 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/evermind%2Feveros-85b37aa2a2bf87c039a27e33652dcfcab2b64f5e90a058e7c4a49b12a8094e02.yml openapi_spec_hash: 31f3985c07b07d8c3bd06e0622fd575e -config_hash: 973539bf905a8a047d5838c0265a00f0 +config_hash: b447a6fa02f16b6e8a243cc143c14df9 diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 3f15e25..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,11 +0,0 @@ -# Changelog - -## 0.0.2 (2026-04-01) - -Full Changelog: [v0.0.1...v0.0.2](https://github.com/evermemos/everos-python/compare/v0.0.1...v0.0.2) - -### Chores - -* update SDK settings ([68703ac](https://github.com/evermemos/everos-python/commit/68703ac328d743e307d9dfd30bd7b52b8d894dba)) -* update SDK settings ([bdb213a](https://github.com/evermemos/everos-python/commit/bdb213a592f0665a038128b222b6ccdc882a29fd)) -* update SDK settings ([5c4bbb3](https://github.com/evermemos/everos-python/commit/5c4bbb3c610ae5bda82cb23c40861d0071066e4b)) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 67c907c..3ca5f33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ pip install git+ssh://git@github.com/evermemos/everos-python.git +$ pip install git+ssh://git@github.com/everos/everos-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -113,7 +113,7 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/evermemos/everos-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/everos/everos-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually diff --git a/LICENSE b/LICENSE index 5d63548..7c2e458 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2026 Everos + Copyright 2026 Ever OS Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index be1bd4b..d3d3795 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# Everos Python API library +# EverOS API library [![PyPI version](https://img.shields.io/pypi/v/everos.svg?label=pypi%20(stable))](https://pypi.org/project/everos/) -The Everos Python library provides convenient access to the Everos REST API from any Python 3.9+ +The EverOS library provides convenient access to the Ever OS REST API from any Python 3.9+ application. The library includes type definitions for all request params and response fields, and offers both synchronous and asynchronous clients powered by [httpx](https://github.com/encode/httpx). @@ -26,23 +26,21 @@ The full API of this library can be found in [api.md](api.md). ```python import os -from everos import Everos +from everos import EverOS -client = Everos( +client = EverOS( api_key=os.environ.get("EVEROS_API_KEY"), # This is the default and can be omitted - # or 'production' | 'test' | 'environment_3'; defaults to "production". - environment="environment_1", ) -add_response = client.v1.memories.create( +add_response = client.v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) print(add_response.data) ``` @@ -54,30 +52,28 @@ so that your API Key is not stored in source control. ## Async usage -Simply import `AsyncEveros` instead of `Everos` and use `await` with each API call: +Simply import `AsyncEverOS` instead of `EverOS` and use `await` with each API call: ```python import os import asyncio -from everos import AsyncEveros +from everos import AsyncEverOS -client = AsyncEveros( +client = AsyncEverOS( api_key=os.environ.get("EVEROS_API_KEY"), # This is the default and can be omitted - # or 'production' | 'test' | 'environment_3'; defaults to "production". - environment="environment_1", ) async def main() -> None: - add_response = await client.v1.memories.create( + add_response = await client.v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) print(add_response.data) @@ -104,23 +100,23 @@ Then you can enable it by instantiating the client with `http_client=DefaultAioH import os import asyncio from everos import DefaultAioHttpClient -from everos import AsyncEveros +from everos import AsyncEverOS async def main() -> None: - async with AsyncEveros( + async with AsyncEverOS( api_key=os.environ.get("EVEROS_API_KEY"), # This is the default and can be omitted http_client=DefaultAioHttpClient(), ) as client: - add_response = await client.v1.memories.create( + add_response = await client.v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) print(add_response.data) @@ -142,9 +138,9 @@ Typed requests and responses provide autocomplete and documentation within your Nested parameters are dictionaries, typed using `TypedDict`, for example: ```python -from everos import Everos +from everos import EverOS -client = Everos() +client = EverOS() settings_api_response = client.v1.settings.update( llm_custom_setting={}, @@ -163,20 +159,20 @@ All errors inherit from `everos.APIError`. ```python import everos -from everos import Everos +from everos import EverOS -client = Everos() +client = EverOS() try: - client.v1.memories.create( + client.v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) except everos.APIConnectionError as e: print("The server could not be reached") @@ -211,24 +207,24 @@ Connection errors (for example, due to a network connectivity problem), 408 Requ You can use the `max_retries` option to configure or disable retry settings: ```python -from everos import Everos +from everos import EverOS # Configure the default for all requests: -client = Everos( +client = EverOS( # default is 2 max_retries=0, ) # Or, configure per-request: -client.with_options(max_retries=5).v1.memories.create( +client.with_options(max_retries=5).v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) ``` @@ -238,29 +234,29 @@ By default requests time out after 1 minute. You can configure this with a `time which accepts a float or an [`httpx.Timeout`](https://www.python-httpx.org/advanced/timeouts/#fine-tuning-the-configuration) object: ```python -from everos import Everos +from everos import EverOS # Configure the default for all requests: -client = Everos( +client = EverOS( # 20 seconds (default is 1 minute) timeout=20.0, ) # More granular control: -client = Everos( +client = EverOS( timeout=httpx.Timeout(60.0, read=5.0, write=10.0, connect=2.0), ) # Override per-request: -client.with_options(timeout=5.0).v1.memories.create( +client.with_options(timeout=5.0).v1.memories.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) ``` @@ -274,10 +270,10 @@ Note that requests that time out are [retried twice by default](#retries). We use the standard library [`logging`](https://docs.python.org/3/library/logging.html) module. -You can enable logging by setting the environment variable `EVEROS_LOG` to `info`. +You can enable logging by setting the environment variable `EVER_OS_LOG` to `info`. ```shell -$ export EVEROS_LOG=info +$ export EVER_OS_LOG=info ``` Or to `debug` for more verbose logging. @@ -299,26 +295,26 @@ if response.my_field is None: The "raw" Response object can be accessed by prefixing `.with_raw_response.` to any HTTP method call, e.g., ```py -from everos import Everos +from everos import EverOS -client = Everos() -response = client.v1.memories.with_raw_response.create( +client = EverOS() +response = client.v1.memories.with_raw_response.add( messages=[{ - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", }], - user_id="user_id", + user_id="user_123", ) print(response.headers.get('X-My-Header')) -memory = response.parse() # get the object that `v1.memories.create()` would have returned +memory = response.parse() # get the object that `v1.memories.add()` would have returned print(memory.data) ``` -These methods return an [`APIResponse`](https://github.com/evermemos/everos-python/tree/main/src/everos/_response.py) object. +These methods return an [`APIResponse`](https://github.com/everos/everos-python/tree/main/src/everos/_response.py) object. -The async client returns an [`AsyncAPIResponse`](https://github.com/evermemos/everos-python/tree/main/src/everos/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. +The async client returns an [`AsyncAPIResponse`](https://github.com/everos/everos-python/tree/main/src/everos/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. #### `.with_streaming_response` @@ -327,15 +323,15 @@ The above interface eagerly reads the full response body when you make the reque To stream the response body, use `.with_streaming_response` instead, which requires a context manager and only reads the response body once you call `.read()`, `.text()`, `.json()`, `.iter_bytes()`, `.iter_text()`, `.iter_lines()` or `.parse()`. In the async client, these are async methods. ```python -with client.v1.memories.with_streaming_response.create( +with client.v1.memories.with_streaming_response.add( messages=[ { - "content": "x", "role": "user", - "timestamp": 0, + "timestamp": 1705318800000, + "content": "Hello, how are you?", } ], - user_id="user_id", + user_id="user_123", ) as response: print(response.headers.get("X-My-Header")) @@ -389,10 +385,10 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c ```python import httpx -from everos import Everos, DefaultHttpxClient +from everos import EverOS, DefaultHttpxClient -client = Everos( - # Or use the `EVEROS_BASE_URL` env var +client = EverOS( + # Or use the `EVER_OS_BASE_URL` env var base_url="http://my.test.server.example.com:8083", http_client=DefaultHttpxClient( proxy="http://my.test.proxy.example.com", @@ -412,9 +408,9 @@ client.with_options(http_client=DefaultHttpxClient(...)) By default the library closes underlying HTTP connections whenever the client is [garbage collected](https://docs.python.org/3/reference/datamodel.html#object.__del__). You can manually close the client using the `.close()` method if desired, or with a context manager that closes when exiting. ```py -from everos import Everos +from everos import EverOS -with Everos() as client: +with EverOS() as client: # make requests here ... @@ -431,7 +427,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/evermemos/everos-python/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/everos/everos-python/issues) with questions, bugs, or suggestions. ### Determining the installed version diff --git a/SECURITY.md b/SECURITY.md index 1124bff..cfb4d4f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -16,7 +16,7 @@ before making any information public. ## Reporting Non-SDK Related Security Issues If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Everos, please follow the respective company's security reporting guidelines. +or products provided by Ever OS, please follow the respective company's security reporting guidelines. --- diff --git a/api.md b/api.md index 0a16971..e31b483 100644 --- a/api.md +++ b/api.md @@ -1,100 +1,154 @@ # V1 +## Groups + Types: ```python -from everos.types import V1QueryTaskStatusResponse +from everos.types.v1 import CreateGroupRequest, GroupAPIResponse, GroupResponse, PatchGroupRequest ``` Methods: -- client.v1.query_task_status(task_id) -> V1QueryTaskStatusResponse +- client.v1.groups.create(\*\*params) -> GroupAPIResponse +- client.v1.groups.retrieve(group_id) -> GroupAPIResponse +- client.v1.groups.patch(group_id, \*\*params) -> GroupAPIResponse -## Memories +## Settings Types: ```python from everos.types.v1 import ( - AddResponse, - ContentItem, - FlushResponse, - MemoryGetResponse, - MemorySearchResponse, + LlmCustomSetting, + LlmProviderConfig, + SettingsAPIResponse, + SettingsResponse, + UpdateSettingsRequest, ) ``` Methods: -- client.v1.memories.create(\*\*params) -> AddResponse -- client.v1.memories.delete(\*\*params) -> None -- client.v1.memories.flush(\*\*params) -> FlushResponse -- client.v1.memories.get(\*\*params) -> MemoryGetResponse -- client.v1.memories.search(\*\*params) -> MemorySearchResponse +- client.v1.settings.retrieve() -> SettingsAPIResponse +- client.v1.settings.update(\*\*params) -> SettingsAPIResponse -### Group +## Senders + +Types: + +```python +from everos.types.v1 import ( + CreateSenderRequest, + PatchSenderRequest, + SenderAPIResponse, + SenderResponse, +) +``` Methods: -- client.v1.memories.group.create(\*\*params) -> AddResponse -- client.v1.memories.group.flush(\*\*params) -> FlushResponse +- client.v1.senders.create(\*\*params) -> SenderAPIResponse +- client.v1.senders.retrieve(sender_id) -> SenderAPIResponse +- client.v1.senders.patch(sender_id, \*\*params) -> SenderAPIResponse -### Agent +## Memories + +Types: + +```python +from everos.types.v1 import ( + AddResponse, + AddResult, + AsyncAddResponse, + AsyncAddResult, + ContentItem, + DeleteMemoriesRequest, + EpisodeItem, + FlushResponse, + FlushResult, + GetMemRequest, + GetMemResponse, + GetMemoriesResponse, + MessageItem, + PersonalAddRequest, + PersonalFlushRequest, + ProfileItem, + RawMessageDto, + SearchMemoriesRequest, + SearchMemoriesResponse, + SearchMemoriesResponseData, +) +``` Methods: -- client.v1.memories.agent.create(\*\*params) -> AddResponse -- client.v1.memories.agent.flush(\*\*params) -> FlushResponse +- client.v1.memories.delete(\*\*params) -> None +- client.v1.memories.add(\*\*params) -> AddResponse +- client.v1.memories.flush(\*\*params) -> FlushResponse +- client.v1.memories.get(\*\*params) -> GetMemoriesResponse +- client.v1.memories.search(\*\*params) -> SearchMemoriesResponse -## Groups +### Agent Types: ```python -from everos.types.v1 import GroupAPIResponse +from everos.types.v1.memories import ( + AgentAddRequest, + AgentCaseItem, + AgentFlushRequest, + AgentMessageItem, + AgentSkillItem, + ToolCall, + ToolCallFunction, +) ``` Methods: -- client.v1.groups.retrieve(group_id) -> GroupAPIResponse -- client.v1.groups.update(group_id, \*\*params) -> GroupAPIResponse -- client.v1.groups.create_or_update(\*\*params) -> GroupAPIResponse +- client.v1.memories.agent.add(\*\*params) -> AddResponse +- client.v1.memories.agent.flush(\*\*params) -> FlushResponse -## Senders +### Group Types: ```python -from everos.types.v1 import SenderAPIResponse +from everos.types.v1.memories import GroupAddRequest, GroupFlushRequest, GroupMessageItem ``` Methods: -- client.v1.senders.retrieve(sender_id) -> SenderAPIResponse -- client.v1.senders.update(sender_id, \*\*params) -> SenderAPIResponse -- client.v1.senders.create_or_update(\*\*params) -> SenderAPIResponse +- client.v1.memories.group.add(\*\*params) -> AddResponse +- client.v1.memories.group.flush(\*\*params) -> FlushResponse ## Object Types: ```python -from everos.types.v1 import ObjectGetPresignedURLResponse +from everos.types.v1 import ( + ObjectSignItem, + ObjectSignItemRequest, + ObjectSignRequest, + ObjectSignResponse, + ObjectSignedInfo, +) ``` Methods: -- client.v1.object.get_presigned_url(\*\*params) -> ObjectGetPresignedURLResponse +- client.v1.object.sign(\*\*params) -> ObjectSignResponse -## Settings +## Tasks Types: ```python -from everos.types.v1 import LlmProviderConfig, SettingsAPIResponse +from everos.types.v1 import GetTaskStatusResponse, TaskStatusResult ``` Methods: -- client.v1.settings.retrieve() -> SettingsAPIResponse -- client.v1.settings.update(\*\*params) -> SettingsAPIResponse +- client.v1.tasks.retrieve(task_id) -> GetTaskStatusResponse diff --git a/bin/check-release-environment b/bin/check-release-environment index 1e951e9..b845b0f 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,6 +2,10 @@ errors=() +if [ -z "${PYPI_TOKEN}" ]; then + errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") +fi + lenErrors=${#errors[@]} if [[ lenErrors -gt 0 ]]; then diff --git a/bin/publish-pypi b/bin/publish-pypi index 5895700..e72ca2f 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -4,8 +4,4 @@ set -eux rm -rf dist mkdir -p dist uv build -if [ -n "${PYPI_TOKEN:-}" ]; then - uv publish --token=$PYPI_TOKEN -else - uv publish -fi +uv publish --token=$PYPI_TOKEN diff --git a/pyproject.toml b/pyproject.toml index 6205708..58c69fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,11 +1,11 @@ [project] name = "everos" -version = "0.0.2" -description = "The official Python library for the everos API" +version = "0.0.1" +description = "The official Python library for the EverOS API" dynamic = ["readme"] license = "Apache-2.0" authors = [ -{ name = "Everos", email = "" }, +{ name = "Ever OS", email = "" }, ] dependencies = [ @@ -37,8 +37,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/evermemos/everos-python" -Repository = "https://github.com/evermemos/everos-python" +Homepage = "https://github.com/everos/everos-python" +Repository = "https://github.com/everos/everos-python" [project.optional-dependencies] aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] @@ -112,7 +112,7 @@ path = "README.md" [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] # replace relative links with absolute links pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' -replacement = '[\1](https://github.com/evermemos/everos-python/tree/main/\g<2>)' +replacement = '[\1](https://github.com/everos/everos-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/everos/__init__.py b/src/everos/__init__.py index 81014ea..3cea963 100644 --- a/src/everos/__init__.py +++ b/src/everos/__init__.py @@ -5,25 +5,14 @@ from . import types from ._types import NOT_GIVEN, Omit, NoneType, NotGiven, Transport, ProxiesTypes, omit, not_given from ._utils import file_from_path -from ._client import ( - ENVIRONMENTS, - Client, - Everos, - Stream, - Timeout, - Transport, - AsyncClient, - AsyncEveros, - AsyncStream, - RequestOptions, -) +from ._client import Client, EverOS, Stream, Timeout, Transport, AsyncClient, AsyncEverOS, AsyncStream, RequestOptions from ._models import BaseModel from ._version import __title__, __version__ from ._response import APIResponse as APIResponse, AsyncAPIResponse as AsyncAPIResponse from ._constants import DEFAULT_TIMEOUT, DEFAULT_MAX_RETRIES, DEFAULT_CONNECTION_LIMITS from ._exceptions import ( APIError, - EverosError, + EverOSError, ConflictError, NotFoundError, APIStatusError, @@ -52,7 +41,7 @@ "not_given", "Omit", "omit", - "EverosError", + "EverOSError", "APIError", "APIStatusError", "APITimeoutError", @@ -72,9 +61,8 @@ "AsyncClient", "Stream", "AsyncStream", - "Everos", - "AsyncEveros", - "ENVIRONMENTS", + "EverOS", + "AsyncEverOS", "file_from_path", "BaseModel", "DEFAULT_TIMEOUT", diff --git a/src/everos/_base_client.py b/src/everos/_base_client.py index f0d5a83..135c61e 100644 --- a/src/everos/_base_client.py +++ b/src/everos/_base_client.py @@ -63,7 +63,7 @@ ) from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping from ._compat import PYDANTIC_V1, model_copy, model_dump -from ._models import GenericModel, SecurityOptions, FinalRequestOptions, validate_type, construct_type +from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type from ._response import ( APIResponse, BaseAPIResponse, @@ -432,27 +432,9 @@ def _make_status_error( ) -> _exceptions.APIStatusError: raise NotImplementedError() - def _auth_headers( - self, - security: SecurityOptions, # noqa: ARG002 - ) -> dict[str, str]: - return {} - - def _auth_query( - self, - security: SecurityOptions, # noqa: ARG002 - ) -> dict[str, str]: - return {} - - def _custom_auth( - self, - security: SecurityOptions, # noqa: ARG002 - ) -> httpx.Auth | None: - return None - def _build_headers(self, options: FinalRequestOptions, *, retries_taken: int = 0) -> httpx.Headers: custom_headers = options.headers or {} - headers_dict = _merge_mappings({**self._auth_headers(options.security), **self.default_headers}, custom_headers) + headers_dict = _merge_mappings(self.default_headers, custom_headers) self._validate_headers(headers_dict, custom_headers) # headers are case-insensitive while dictionaries are not. @@ -524,7 +506,7 @@ def _build_request( raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`") headers = self._build_headers(options, retries_taken=retries_taken) - params = _merge_mappings({**self._auth_query(options.security), **self.default_query}, options.params) + params = _merge_mappings(self.default_query, options.params) content_type = headers.get("Content-Type") files = options.files @@ -689,6 +671,7 @@ def default_headers(self) -> dict[str, str | Omit]: "Content-Type": "application/json", "User-Agent": self.user_agent, **self.platform_headers(), + **self.auth_headers, **self._custom_headers, } @@ -1007,9 +990,8 @@ def request( self._prepare_request(request) kwargs: HttpxSendArgs = {} - custom_auth = self._custom_auth(options.security) - if custom_auth is not None: - kwargs["auth"] = custom_auth + if self.custom_auth is not None: + kwargs["auth"] = self.custom_auth if options.follow_redirects is not None: kwargs["follow_redirects"] = options.follow_redirects @@ -1970,7 +1952,6 @@ def make_request_options( idempotency_key: str | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, post_parser: PostParser | NotGiven = not_given, - security: SecurityOptions | None = None, ) -> RequestOptions: """Create a dict of type RequestOptions without keys of NotGiven values.""" options: RequestOptions = {} @@ -1996,9 +1977,6 @@ def make_request_options( # internal options["post_parser"] = post_parser # type: ignore - if security is not None: - options["security"] = security - return options diff --git a/src/everos/_client.py b/src/everos/_client.py index c425616..4794eeb 100644 --- a/src/everos/_client.py +++ b/src/everos/_client.py @@ -3,8 +3,8 @@ from __future__ import annotations import os -from typing import TYPE_CHECKING, Any, Dict, Mapping, cast -from typing_extensions import Self, Literal, override +from typing import TYPE_CHECKING, Any, Mapping +from typing_extensions import Self, override import httpx @@ -21,10 +21,9 @@ ) from ._utils import is_given, get_async_library from ._compat import cached_property -from ._models import SecurityOptions from ._version import __version__ from ._streaming import Stream as Stream, AsyncStream as AsyncStream -from ._exceptions import EverosError, APIStatusError +from ._exceptions import EverOSError, APIStatusError from ._base_client import ( DEFAULT_MAX_RETRIES, SyncAPIClient, @@ -35,38 +34,18 @@ from .resources import v1 from .resources.v1.v1 import V1Resource, AsyncV1Resource -__all__ = [ - "ENVIRONMENTS", - "Timeout", - "Transport", - "ProxiesTypes", - "RequestOptions", - "Everos", - "AsyncEveros", - "Client", - "AsyncClient", -] - -ENVIRONMENTS: Dict[str, str] = { - "production": "http://localhost:9527", - "environment_1": "https://dev-gateway.aws.evermind.ai", - "test": "https://test-gateway.aws.evermind.ai", - "environment_3": "https://api.evermind.ai", -} - - -class Everos(SyncAPIClient): +__all__ = ["Timeout", "Transport", "ProxiesTypes", "RequestOptions", "EverOS", "AsyncEverOS", "Client", "AsyncClient"] + + +class EverOS(SyncAPIClient): # client options api_key: str - _environment: Literal["production", "environment_1", "test", "environment_3"] | NotGiven - def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1", "test", "environment_3"] | NotGiven = not_given, - base_url: str | httpx.URL | None | NotGiven = not_given, + base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -85,43 +64,22 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new synchronous Everos client instance. + """Construct a new synchronous EverOS client instance. This automatically infers the `api_key` argument from the `EVEROS_API_KEY` environment variable if it is not provided. """ if api_key is None: api_key = os.environ.get("EVEROS_API_KEY") if api_key is None: - raise EverosError( + raise EverOSError( "The api_key client option must be set either by passing api_key to the client or by setting the EVEROS_API_KEY environment variable" ) self.api_key = api_key - self._environment = environment - - base_url_env = os.environ.get("EVEROS_BASE_URL") - if is_given(base_url) and base_url is not None: - # cast required because mypy doesn't understand the type narrowing - base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] - elif is_given(environment): - if base_url_env and base_url is not None: - raise ValueError( - "Ambiguous URL; The `EVEROS_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", - ) - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc - elif base_url_env is not None: - base_url = base_url_env - else: - self._environment = environment = "production" - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc + if base_url is None: + base_url = os.environ.get("EVER_OS_BASE_URL") + if base_url is None: + base_url = f"https://api.evermind.ai" super().__init__( version=__version__, @@ -133,35 +91,35 @@ def __init__( custom_query=default_query, _strict_response_validation=_strict_response_validation, ) + # custom: inject multimodal-aware MemoriesResource + from .lib._multimodal import MemoriesResourceWithMultimodal as _MemMultimodal + from .resources.v1.v1 import V1Resource as _V1Resource + _v1 = _V1Resource(self) + _v1.__dict__["memories"] = _MemMultimodal(self) + self.__dict__["v1"] = _v1 @cached_property def v1(self) -> V1Resource: - """Async task status tracking""" from .resources.v1 import V1Resource return V1Resource(self) @cached_property - def with_raw_response(self) -> EverosWithRawResponse: - return EverosWithRawResponse(self) + def with_raw_response(self) -> EverOSWithRawResponse: + return EverOSWithRawResponse(self) @cached_property - def with_streaming_response(self) -> EverosWithStreamedResponse: - return EverosWithStreamedResponse(self) + def with_streaming_response(self) -> EverOSWithStreamedResponse: + return EverOSWithStreamedResponse(self) @property @override def qs(self) -> Querystring: return Querystring(array_format="comma") - @override - def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: - return { - **(self._bearer_auth if security.get("bearer_auth", False) else {}), - } - @property - def _bearer_auth(self) -> dict[str, str]: + @override + def auth_headers(self) -> dict[str, str]: api_key = self.api_key return {"Authorization": f"Bearer {api_key}"} @@ -178,7 +136,6 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1", "test", "environment_3"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.Client | None = None, @@ -214,7 +171,6 @@ def copy( return self.__class__( api_key=api_key or self.api_key, base_url=base_url or self.base_url, - environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, @@ -261,18 +217,15 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class AsyncEveros(AsyncAPIClient): +class AsyncEverOS(AsyncAPIClient): # client options api_key: str - _environment: Literal["production", "environment_1", "test", "environment_3"] | NotGiven - def __init__( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1", "test", "environment_3"] | NotGiven = not_given, - base_url: str | httpx.URL | None | NotGiven = not_given, + base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, max_retries: int = DEFAULT_MAX_RETRIES, default_headers: Mapping[str, str] | None = None, @@ -291,43 +244,22 @@ def __init__( # part of our public interface in the future. _strict_response_validation: bool = False, ) -> None: - """Construct a new async AsyncEveros client instance. + """Construct a new async AsyncEverOS client instance. This automatically infers the `api_key` argument from the `EVEROS_API_KEY` environment variable if it is not provided. """ if api_key is None: api_key = os.environ.get("EVEROS_API_KEY") if api_key is None: - raise EverosError( + raise EverOSError( "The api_key client option must be set either by passing api_key to the client or by setting the EVEROS_API_KEY environment variable" ) self.api_key = api_key - self._environment = environment - - base_url_env = os.environ.get("EVEROS_BASE_URL") - if is_given(base_url) and base_url is not None: - # cast required because mypy doesn't understand the type narrowing - base_url = cast("str | httpx.URL", base_url) # pyright: ignore[reportUnnecessaryCast] - elif is_given(environment): - if base_url_env and base_url is not None: - raise ValueError( - "Ambiguous URL; The `EVEROS_BASE_URL` env var and the `environment` argument are given. If you want to use the environment, you must pass base_url=None", - ) - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc - elif base_url_env is not None: - base_url = base_url_env - else: - self._environment = environment = "production" - - try: - base_url = ENVIRONMENTS[environment] - except KeyError as exc: - raise ValueError(f"Unknown environment: {environment}") from exc + if base_url is None: + base_url = os.environ.get("EVER_OS_BASE_URL") + if base_url is None: + base_url = f"https://api.evermind.ai" super().__init__( version=__version__, @@ -339,35 +271,35 @@ def __init__( custom_query=default_query, _strict_response_validation=_strict_response_validation, ) + # custom: inject multimodal-aware AsyncMemoriesResource + from .lib._multimodal import AsyncMemoriesResourceWithMultimodal as _AsyncMemMultimodal + from .resources.v1.v1 import AsyncV1Resource as _AsyncV1Resource + _v1 = _AsyncV1Resource(self) + _v1.__dict__["memories"] = _AsyncMemMultimodal(self) + self.__dict__["v1"] = _v1 @cached_property def v1(self) -> AsyncV1Resource: - """Async task status tracking""" from .resources.v1 import AsyncV1Resource return AsyncV1Resource(self) @cached_property - def with_raw_response(self) -> AsyncEverosWithRawResponse: - return AsyncEverosWithRawResponse(self) + def with_raw_response(self) -> AsyncEverOSWithRawResponse: + return AsyncEverOSWithRawResponse(self) @cached_property - def with_streaming_response(self) -> AsyncEverosWithStreamedResponse: - return AsyncEverosWithStreamedResponse(self) + def with_streaming_response(self) -> AsyncEverOSWithStreamedResponse: + return AsyncEverOSWithStreamedResponse(self) @property @override def qs(self) -> Querystring: return Querystring(array_format="comma") - @override - def _auth_headers(self, security: SecurityOptions) -> dict[str, str]: - return { - **(self._bearer_auth if security.get("bearer_auth", False) else {}), - } - @property - def _bearer_auth(self) -> dict[str, str]: + @override + def auth_headers(self) -> dict[str, str]: api_key = self.api_key return {"Authorization": f"Bearer {api_key}"} @@ -384,7 +316,6 @@ def copy( self, *, api_key: str | None = None, - environment: Literal["production", "environment_1", "test", "environment_3"] | None = None, base_url: str | httpx.URL | None = None, timeout: float | Timeout | None | NotGiven = not_given, http_client: httpx.AsyncClient | None = None, @@ -420,7 +351,6 @@ def copy( return self.__class__( api_key=api_key or self.api_key, base_url=base_url or self.base_url, - environment=environment or self._environment, timeout=self.timeout if isinstance(timeout, NotGiven) else timeout, http_client=http_client, max_retries=max_retries if is_given(max_retries) else self.max_retries, @@ -467,62 +397,58 @@ def _make_status_error( return APIStatusError(err_msg, response=response, body=body) -class EverosWithRawResponse: - _client: Everos +class EverOSWithRawResponse: + _client: EverOS - def __init__(self, client: Everos) -> None: + def __init__(self, client: EverOS) -> None: self._client = client @cached_property def v1(self) -> v1.V1ResourceWithRawResponse: - """Async task status tracking""" from .resources.v1 import V1ResourceWithRawResponse return V1ResourceWithRawResponse(self._client.v1) -class AsyncEverosWithRawResponse: - _client: AsyncEveros +class AsyncEverOSWithRawResponse: + _client: AsyncEverOS - def __init__(self, client: AsyncEveros) -> None: + def __init__(self, client: AsyncEverOS) -> None: self._client = client @cached_property def v1(self) -> v1.AsyncV1ResourceWithRawResponse: - """Async task status tracking""" from .resources.v1 import AsyncV1ResourceWithRawResponse return AsyncV1ResourceWithRawResponse(self._client.v1) -class EverosWithStreamedResponse: - _client: Everos +class EverOSWithStreamedResponse: + _client: EverOS - def __init__(self, client: Everos) -> None: + def __init__(self, client: EverOS) -> None: self._client = client @cached_property def v1(self) -> v1.V1ResourceWithStreamingResponse: - """Async task status tracking""" from .resources.v1 import V1ResourceWithStreamingResponse return V1ResourceWithStreamingResponse(self._client.v1) -class AsyncEverosWithStreamedResponse: - _client: AsyncEveros +class AsyncEverOSWithStreamedResponse: + _client: AsyncEverOS - def __init__(self, client: AsyncEveros) -> None: + def __init__(self, client: AsyncEverOS) -> None: self._client = client @cached_property def v1(self) -> v1.AsyncV1ResourceWithStreamingResponse: - """Async task status tracking""" from .resources.v1 import AsyncV1ResourceWithStreamingResponse return AsyncV1ResourceWithStreamingResponse(self._client.v1) -Client = Everos +Client = EverOS -AsyncClient = AsyncEveros +AsyncClient = AsyncEverOS diff --git a/src/everos/_exceptions.py b/src/everos/_exceptions.py index bad1672..c7c803e 100644 --- a/src/everos/_exceptions.py +++ b/src/everos/_exceptions.py @@ -18,11 +18,11 @@ ] -class EverosError(Exception): +class EverOSError(Exception): pass -class APIError(EverosError): +class APIError(EverOSError): message: str request: httpx.Request diff --git a/src/everos/_models.py b/src/everos/_models.py index e22dd2a..29070e0 100644 --- a/src/everos/_models.py +++ b/src/everos/_models.py @@ -791,10 +791,6 @@ def _create_pydantic_model(type_: _T) -> Type[RootModel[_T]]: return RootModel[type_] # type: ignore -class SecurityOptions(TypedDict, total=False): - bearer_auth: bool - - class FinalRequestOptionsInput(TypedDict, total=False): method: Required[str] url: Required[str] @@ -808,7 +804,6 @@ class FinalRequestOptionsInput(TypedDict, total=False): json_data: Body extra_json: AnyMapping follow_redirects: bool - security: SecurityOptions @final @@ -823,7 +818,6 @@ class FinalRequestOptions(pydantic.BaseModel): idempotency_key: Union[str, None] = None post_parser: Union[Callable[[Any], Any], NotGiven] = NotGiven() follow_redirects: Union[bool, None] = None - security: SecurityOptions = {"bearer_auth": True} content: Union[bytes, bytearray, IO[bytes], Iterable[bytes], AsyncIterable[bytes], None] = None # It should be noted that we cannot use `json` here as that would override diff --git a/src/everos/_resource.py b/src/everos/_resource.py index 3ca9433..f29e404 100644 --- a/src/everos/_resource.py +++ b/src/everos/_resource.py @@ -8,13 +8,13 @@ import anyio if TYPE_CHECKING: - from ._client import Everos, AsyncEveros + from ._client import EverOS, AsyncEverOS class SyncAPIResource: - _client: Everos + _client: EverOS - def __init__(self, client: Everos) -> None: + def __init__(self, client: EverOS) -> None: self._client = client self._get = client.get self._post = client.post @@ -28,9 +28,9 @@ def _sleep(self, seconds: float) -> None: class AsyncAPIResource: - _client: AsyncEveros + _client: AsyncEverOS - def __init__(self, client: AsyncEveros) -> None: + def __init__(self, client: AsyncEverOS) -> None: self._client = client self._get = client.get self._post = client.post diff --git a/src/everos/_response.py b/src/everos/_response.py index 7f733bf..8d2f175 100644 --- a/src/everos/_response.py +++ b/src/everos/_response.py @@ -29,7 +29,7 @@ from ._models import BaseModel, is_basemodel from ._constants import RAW_RESPONSE_HEADER, OVERRIDE_CAST_TO_HEADER from ._streaming import Stream, AsyncStream, is_stream_class_type, extract_stream_chunk_type -from ._exceptions import EverosError, APIResponseValidationError +from ._exceptions import EverOSError, APIResponseValidationError if TYPE_CHECKING: from ._models import FinalRequestOptions @@ -563,7 +563,7 @@ def __init__(self) -> None: ) -class StreamAlreadyConsumed(EverosError): +class StreamAlreadyConsumed(EverOSError): """ Attempted to read or stream content, but the content has already been streamed. diff --git a/src/everos/_streaming.py b/src/everos/_streaming.py index 5e47a83..8a34414 100644 --- a/src/everos/_streaming.py +++ b/src/everos/_streaming.py @@ -12,7 +12,7 @@ from ._utils import extract_type_var_from_base if TYPE_CHECKING: - from ._client import Everos, AsyncEveros + from ._client import EverOS, AsyncEverOS from ._models import FinalRequestOptions @@ -31,7 +31,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: Everos, + client: EverOS, options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response @@ -96,7 +96,7 @@ def __init__( *, cast_to: type[_T], response: httpx.Response, - client: AsyncEveros, + client: AsyncEverOS, options: Optional[FinalRequestOptions] = None, ) -> None: self.response = response diff --git a/src/everos/_types.py b/src/everos/_types.py index eca6477..42c8888 100644 --- a/src/everos/_types.py +++ b/src/everos/_types.py @@ -36,7 +36,7 @@ from httpx import URL, Proxy, Timeout, Response, BaseTransport, AsyncBaseTransport if TYPE_CHECKING: - from ._models import BaseModel, SecurityOptions + from ._models import BaseModel from ._response import APIResponse, AsyncAPIResponse Transport = BaseTransport @@ -121,7 +121,6 @@ class RequestOptions(TypedDict, total=False): extra_json: AnyMapping idempotency_key: str follow_redirects: bool - security: SecurityOptions # Sentinel class used until PEP 0661 is accepted diff --git a/src/everos/_utils/_logs.py b/src/everos/_utils/_logs.py index 98b7480..1514cea 100644 --- a/src/everos/_utils/_logs.py +++ b/src/everos/_utils/_logs.py @@ -14,7 +14,7 @@ def _basic_config() -> None: def setup_logging() -> None: - env = os.environ.get("EVEROS_LOG") + env = os.environ.get("EVER_OS_LOG") if env == "debug": _basic_config() logger.setLevel(logging.DEBUG) diff --git a/src/everos/_version.py b/src/everos/_version.py index 9ad6d9e..81c5c09 100644 --- a/src/everos/_version.py +++ b/src/everos/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "everos" -__version__ = "0.0.2" # x-release-please-version +__version__ = "0.0.1" # x-release-please-version diff --git a/src/everos/lib/__init__.py b/src/everos/lib/__init__.py new file mode 100644 index 0000000..1222675 --- /dev/null +++ b/src/everos/lib/__init__.py @@ -0,0 +1,12 @@ +"""EverOS SDK custom helpers — multimodal upload support.""" + +from ._errors import UploadError, MultimodalError, FileResolveError +from ._multimodal import MemoriesResourceWithMultimodal, AsyncMemoriesResourceWithMultimodal + +__all__ = [ + "MultimodalError", + "FileResolveError", + "UploadError", + "MemoriesResourceWithMultimodal", + "AsyncMemoriesResourceWithMultimodal", +] diff --git a/src/everos/lib/_detect.py b/src/everos/lib/_detect.py new file mode 100644 index 0000000..f6dbcd9 --- /dev/null +++ b/src/everos/lib/_detect.py @@ -0,0 +1,116 @@ +"""Scan messages for content items that reference uploadable files. + +Detection rules (applied to each content item where type != 'text'): + - uri starts with http:// or https:// → HTTP file, download then upload + - uri is an existing local file path → local file, upload directly + - anything else → treat as existing object_key, pass through +""" + +from __future__ import annotations + +import os +from typing import Any, List, Literal +from dataclasses import dataclass + +# ContentItem type → object.sign file_type +_CONTENT_TYPE_TO_FILE_TYPE: dict[str, Literal["image", "file", "video"]] = { + "image": "image", + "audio": "file", + "doc": "file", + "pdf": "file", + "html": "file", + "email": "file", + # "video": "video", # reserved, not in current ContentItemParam +} + + +@dataclass +class UploadTask: + """Describes a single content item that needs to be uploaded.""" + + msg_idx: int + """Index of the message in the messages list.""" + + content_idx: int + """Index of the content item within message.content.""" + + uri: str + """Original uri value from the content item.""" + + uri_type: Literal["local", "http"] + """Whether the uri is a local file path or an HTTP(S) URL.""" + + content_type: str + """ContentItem type (e.g. 'image', 'doc').""" + + file_type: Literal["image", "file", "video"] + """file_type for the object.sign API.""" + + file_name: str | None + """ContentItem name field (may be None).""" + + file_ext: str | None + """ContentItem ext field (may be None).""" + + +def _is_http_uri(uri: str) -> bool: + return uri.startswith("http://") or uri.startswith("https://") + + +def _is_local_file(uri: str) -> bool: + """Return True only if uri refers to an existing local file. + + MinIO object keys contain server-generated UUID segments and will never + match a local path, so os.path.isfile() is a reliable discriminator. + """ + if "://" in uri: + return False + return os.path.isfile(os.path.expanduser(uri)) + + +def scan_messages(messages: list[dict[str, Any]]) -> List[UploadTask]: + """Scan messages and return a list of UploadTask for content items that need uploading. + + Returns an empty list when no uploads are needed (caller can skip multimodal path). + Does NOT modify messages. + """ + tasks: list[UploadTask] = [] + + for msg_idx, msg in enumerate(messages): + content = msg.get("content") + + # str shorthand → plain text, skip + if content is None or isinstance(content, str): + continue + + for ci_idx, item in enumerate(content): + item_type = item.get("type", "text") + + if item_type == "text": + continue + + uri = item.get("uri") + if not uri or not isinstance(uri, str): + continue + + if _is_http_uri(uri): + uri_type: Literal["local", "http"] = "http" + elif _is_local_file(uri): + uri_type = "local" + else: + continue # already an object_key or unsupported scheme + + file_type = _CONTENT_TYPE_TO_FILE_TYPE.get(item_type, "file") + + tasks.append(UploadTask( + msg_idx=msg_idx, + content_idx=ci_idx, + uri=uri, + uri_type=uri_type, + content_type=item_type, + file_type=file_type, + file_name=item.get("name"), + file_ext=item.get("ext"), + )) + + return tasks diff --git a/src/everos/lib/_errors.py b/src/everos/lib/_errors.py new file mode 100644 index 0000000..13d407b --- /dev/null +++ b/src/everos/lib/_errors.py @@ -0,0 +1,18 @@ +"""Multimodal upload exceptions.""" + +from __future__ import annotations + + +class MultimodalError(Exception): + """Base exception for multimodal upload operations.""" + pass + + +class FileResolveError(MultimodalError): + """Failed to resolve a file input (path not found, URL download failure, size exceeded, etc.).""" + pass + + +class UploadError(MultimodalError): + """Failed to sign or upload a file to S3.""" + pass diff --git a/src/everos/lib/_files.py b/src/everos/lib/_files.py new file mode 100644 index 0000000..73ec38b --- /dev/null +++ b/src/everos/lib/_files.py @@ -0,0 +1,208 @@ +"""File input abstraction and streaming file resolution. + +ResolvedFile holds a local file path (never raw bytes), so uploads are always +streamed rather than loaded into memory. +""" + +from __future__ import annotations + +import os +import tempfile +import mimetypes +from typing import BinaryIO +from pathlib import Path +from dataclasses import field, dataclass +from urllib.parse import unquote, urlparse + +import httpx + +from ._errors import FileResolveError + +_DEFAULT_MAX_DOWNLOAD_SIZE = 100 * 1024 * 1024 # 100 MB +_DEFAULT_DOWNLOAD_TIMEOUT = 60.0 # seconds +_STREAM_CHUNK_SIZE = 64 * 1024 # 64 KB + + +@dataclass +class FileInput: + """Unified file input. Exactly one of path / url must be set. + + Examples:: + + FileInput(path="./photo.jpg") + FileInput(url="https://example.com/image.png") + """ + + path: str | Path | None = None + url: str | None = None + content_type: str | None = None + filename: str | None = None + + def __post_init__(self) -> None: + sources = sum(x is not None for x in [self.path, self.url]) + if sources == 0: + raise ValueError("FileInput requires exactly one of: path, url") + if sources > 1: + raise ValueError("FileInput accepts only one of: path, url") + + +@dataclass +class ResolvedFile: + """Resolved file — holds a local path, never raw bytes. + + Attributes: + file_path: Absolute path to the local file (original or downloaded temp file). + content_type: MIME type. + filename: Original filename. + size: File size in bytes. + is_temp: True if this is a downloaded temp file that must be cleaned up. + """ + + file_path: Path + content_type: str + filename: str + size: int + is_temp: bool = field(default=False) + + def cleanup(self) -> None: + """Delete temp file if applicable. Safe to call multiple times.""" + if self.is_temp and self.file_path.exists(): + self.file_path.unlink() + + def open(self) -> BinaryIO: + """Open for streaming read.""" + return open(self.file_path, "rb") # type: ignore[return-value] + + +# ── Public resolution functions ─────────────────────────────────────────────── + + +def resolve_file( + file_input: FileInput, + *, + max_download_size: int = _DEFAULT_MAX_DOWNLOAD_SIZE, + download_timeout: float = _DEFAULT_DOWNLOAD_TIMEOUT, +) -> ResolvedFile: + """Resolve a FileInput to a ResolvedFile (synchronous).""" + if file_input.path is not None: + return _resolve_from_path(file_input) + else: + return _resolve_from_url(file_input, max_download_size, download_timeout) + + +async def async_resolve_file( + file_input: FileInput, + *, + max_download_size: int = _DEFAULT_MAX_DOWNLOAD_SIZE, + download_timeout: float = _DEFAULT_DOWNLOAD_TIMEOUT, +) -> ResolvedFile: + """Resolve a FileInput to a ResolvedFile (asynchronous).""" + if file_input.path is not None: + import anyio + return await anyio.to_thread.run_sync(lambda: _resolve_from_path(file_input)) # type: ignore[attr-defined] + else: + return await _async_resolve_from_url(file_input, max_download_size, download_timeout) + + +# ── Internal helpers ────────────────────────────────────────────────────────── + + +def _resolve_from_path(fi: FileInput) -> ResolvedFile: + p = Path(os.path.expanduser(str(fi.path))).resolve() + if not p.exists(): + raise FileResolveError(f"File not found: {p}") + if not p.is_file(): + raise FileResolveError(f"Not a file: {p}") + + size = p.stat().st_size + ct = fi.content_type or mimetypes.guess_type(str(p))[0] or "application/octet-stream" + fn = fi.filename or p.name + return ResolvedFile(file_path=p, content_type=ct, filename=fn, size=size, is_temp=False) + + +def _resolve_from_url(fi: FileInput, max_size: int, timeout: float) -> ResolvedFile: + """Stream-download URL to a temp file; abort if size exceeds max_size.""" + tmp_fd, tmp_path = tempfile.mkstemp(prefix="everos_") + tmp_file = Path(tmp_path) + downloaded = 0 + ct_header = "application/octet-stream" + + try: + with httpx.Client(timeout=timeout, follow_redirects=True) as client: + with client.stream("GET", fi.url) as resp: # type: ignore[arg-type] + resp.raise_for_status() + ct_header = resp.headers.get("content-type", ct_header).split(";")[0].strip() + + with os.fdopen(tmp_fd, "wb") as f: + for chunk in resp.iter_bytes(chunk_size=_STREAM_CHUNK_SIZE): + downloaded += len(chunk) + if downloaded > max_size: + raise FileResolveError( + f"Download from {fi.url} exceeded {max_size} bytes limit " + f"({downloaded} bytes so far). " + "Use the low-level object.sign() API for large files." + ) + f.write(chunk) + tmp_fd = -1 # fdopen closed it + + except FileResolveError: + tmp_file.unlink(missing_ok=True) + raise + except Exception as exc: + tmp_file.unlink(missing_ok=True) + if tmp_fd >= 0: + os.close(tmp_fd) + raise FileResolveError(f"Failed to download {fi.url}: {exc}") from exc + + ct = fi.content_type or ct_header + fn = fi.filename or _filename_from_url(fi.url) # type: ignore[arg-type] + return ResolvedFile( + file_path=tmp_file, content_type=ct, filename=fn, + size=downloaded, is_temp=True, + ) + + +async def _async_resolve_from_url(fi: FileInput, max_size: int, timeout: float) -> ResolvedFile: + """Async streaming download to temp file.""" + tmp_fd, tmp_path = tempfile.mkstemp(prefix="everos_") + tmp_file = Path(tmp_path) + downloaded = 0 + ct_header = "application/octet-stream" + + try: + async with httpx.AsyncClient(timeout=timeout, follow_redirects=True) as client: + async with client.stream("GET", fi.url) as resp: # type: ignore[arg-type] + resp.raise_for_status() + ct_header = resp.headers.get("content-type", ct_header).split(";")[0].strip() + + with os.fdopen(tmp_fd, "wb") as f: + async for chunk in resp.aiter_bytes(chunk_size=_STREAM_CHUNK_SIZE): + downloaded += len(chunk) + if downloaded > max_size: + raise FileResolveError( + f"Download from {fi.url} exceeded {max_size} bytes limit." + ) + f.write(chunk) + tmp_fd = -1 + + except FileResolveError: + tmp_file.unlink(missing_ok=True) + raise + except Exception as exc: + tmp_file.unlink(missing_ok=True) + if tmp_fd >= 0: + os.close(tmp_fd) + raise FileResolveError(f"Failed to download {fi.url}: {exc}") from exc + + ct = fi.content_type or ct_header + fn = fi.filename or _filename_from_url(fi.url) # type: ignore[arg-type] + return ResolvedFile( + file_path=tmp_file, content_type=ct, filename=fn, + size=downloaded, is_temp=True, + ) + + +def _filename_from_url(url: str) -> str: + parsed = urlparse(url) + name = Path(unquote(parsed.path)).name + return name if name else "download" diff --git a/src/everos/lib/_multimodal.py b/src/everos/lib/_multimodal.py new file mode 100644 index 0000000..b70285e --- /dev/null +++ b/src/everos/lib/_multimodal.py @@ -0,0 +1,348 @@ +"""Multimodal add orchestration: detect → resolve → batch sign → concurrent upload → replace uri → add. + +MemoriesResourceWithMultimodal overrides add() while keeping the same signature. +The user calls client.v1.memories.add() exactly as before; the SDK handles uploads transparently. +""" + +from __future__ import annotations + +import os +import copy +import logging +from typing import Any, List, Iterable, Optional, cast +from typing_extensions import override +from concurrent.futures import Future, ThreadPoolExecutor, as_completed + +import httpx + +from ._files import FileInput, ResolvedFile, resolve_file, async_resolve_file +from .._types import Body, Omit, Query, Headers, NotGiven, omit, not_given +from ._detect import UploadTask, scan_messages +from ._errors import MultimodalError, FileResolveError +from ._upload import SignedFile, UploadResult, batch_sign, s3_post_upload, async_batch_sign, async_s3_post_upload +from ..types.v1.add_response import AddResponse +from ..types.v1.message_item_param import MessageItemParam +from ..resources.v1.memories.memories import MemoriesResource, AsyncMemoriesResource + +log: logging.Logger = logging.getLogger("everos") + +_DEFAULT_MAX_WORKERS = 4 + + +# ── Sync resource override ──────────────────────────────────────────────────── + + +class MemoriesResourceWithMultimodal(MemoriesResource): + """Inherits MemoriesResource and overrides add() with transparent multimodal upload.""" + + @override + def add( + self, + *, + messages: Iterable[MessageItemParam], + user_id: str, + async_mode: bool | Omit = omit, + session_id: Optional[str] | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AddResponse: + """add() with automatic multimodal upload. + + Scans content items in each message: + - uri is a local file path → sign + upload → replace with object_key + - uri is an http(s) URL → download → sign + upload → replace with object_key + - uri is already an object_key (or any other value) → pass through unchanged + + Signature is identical to the generated add(). + """ + msg_list = _materialise(messages) + upload_tasks = scan_messages(msg_list) + + if not upload_tasks: + log.debug("No uploadable content detected, passing through to add()") + return super().add( + messages=cast(Iterable[MessageItemParam], msg_list), + user_id=user_id, async_mode=async_mode, + session_id=session_id, extra_headers=extra_headers, + extra_query=extra_query, extra_body=extra_body, timeout=timeout, + ) + + log.debug("Multimodal detected: %d file(s) to upload", len(upload_tasks)) + for i, t in enumerate(upload_tasks): + log.debug( + " [%d] messages[%d].content[%d] type=%s uri=%s (%s)", + i, t.msg_idx, t.content_idx, t.content_type, t.uri, t.uri_type, + ) + + msg_list = copy.deepcopy(msg_list) + resolved_files: List[ResolvedFile] = [] + + try: + _resolve_all(upload_tasks, resolved_files) + signed_files = batch_sign(self._client, upload_tasks, resolved_files) + results = _concurrent_upload(signed_files, upload_tasks) + _replace_uris(msg_list, upload_tasks, results) + log.debug("Multimodal upload complete: %d file(s), calling add()", len(upload_tasks)) + return super().add( + messages=cast(Iterable[MessageItemParam], msg_list), + user_id=user_id, async_mode=async_mode, + session_id=session_id, extra_headers=extra_headers, + extra_query=extra_query, extra_body=extra_body, timeout=timeout, + ) + + finally: + _cleanup(resolved_files) + + +# ── Async resource override ─────────────────────────────────────────────────── + + +class AsyncMemoriesResourceWithMultimodal(AsyncMemoriesResource): + """Async version — mirrors MemoriesResourceWithMultimodal.""" + + @override + async def add( + self, + *, + messages: Iterable[MessageItemParam], + user_id: str, + async_mode: bool | Omit = omit, + session_id: Optional[str] | Omit = omit, + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> AddResponse: + """Async add() with automatic multimodal upload. Signature identical to generated add().""" + msg_list = _materialise(messages) + upload_tasks = scan_messages(msg_list) + + if not upload_tasks: + log.debug("No uploadable content detected, passing through to add() (async)") + return await super().add( + messages=cast(Iterable[MessageItemParam], msg_list), + user_id=user_id, async_mode=async_mode, + session_id=session_id, extra_headers=extra_headers, + extra_query=extra_query, extra_body=extra_body, timeout=timeout, + ) + + log.debug("Multimodal detected: %d file(s) to upload (async)", len(upload_tasks)) + + msg_list = copy.deepcopy(msg_list) + resolved_files: List[ResolvedFile] = [] + + try: + await _async_resolve_all(upload_tasks, resolved_files) + signed_files = await async_batch_sign(self._client, upload_tasks, resolved_files) + results = await _async_concurrent_upload(signed_files, upload_tasks) + _replace_uris(msg_list, upload_tasks, results) + log.debug("Multimodal upload complete: %d file(s), calling add() (async)", len(upload_tasks)) + return await super().add( + messages=cast(Iterable[MessageItemParam], msg_list), + user_id=user_id, async_mode=async_mode, + session_id=session_id, extra_headers=extra_headers, + extra_query=extra_query, extra_body=extra_body, timeout=timeout, + ) + + finally: + _cleanup(resolved_files) + + +# ── Shared helpers ──────────────────────────────────────────────────────────── + + +def _materialise(messages: Iterable[MessageItemParam]) -> list[dict[str, Any]]: + """Convert iterable of messages to list[dict] for scanning and mutation.""" + return [dict(m) if isinstance(m, dict) else m for m in messages] # type: ignore[misc] + + +def _resolve_all(upload_tasks: List[UploadTask], resolved_files: List[ResolvedFile]) -> None: + """Resolve files sequentially (preserves order).""" + for task in upload_tasks: + try: + fi = FileInput(path=task.uri) if task.uri_type == "local" else FileInput(url=task.uri) + resolved_files.append(resolve_file(fi)) + log.debug( + "Resolved %s → %d bytes %s", + task.uri, resolved_files[-1].size, resolved_files[-1].content_type, + ) + except FileResolveError: + raise + except Exception as exc: + raise FileResolveError( + f"Failed to resolve messages[{task.msg_idx}].content[{task.content_idx}] " + f"(uri={task.uri}): {exc}" + ) from exc + + +async def _async_resolve_all( + upload_tasks: List[UploadTask], resolved_files: List[ResolvedFile] +) -> None: + """Async parallel resolve (preserves order via gather). + + On failure, cleans up ALL already-resolved temp files (both those appended to + resolved_files and those only present in the gather results list). + """ + import asyncio + + async def _resolve_one(task: UploadTask) -> ResolvedFile: + fi = FileInput(path=task.uri) if task.uri_type == "local" else FileInput(url=task.uri) + return await async_resolve_file(fi) + + coros = [_resolve_one(t) for t in upload_tasks] + results = await asyncio.gather(*coros, return_exceptions=True) + + for i, result in enumerate(results): + if isinstance(result, Exception): + # Clean up ALL resolved temp files: those already appended AND those + # only present in results (gather ran them concurrently). + for r in results: + if isinstance(r, ResolvedFile): + r.cleanup() + task = upload_tasks[i] + raise FileResolveError( + f"Failed to resolve messages[{task.msg_idx}].content[{task.content_idx}] " + f"(uri={task.uri}): {result}" + ) from result + resolved_files.append(result) # type: ignore[arg-type] + log.debug("Resolved %s → %d bytes", upload_tasks[i].uri, result.size) # type: ignore[union-attr] + + +def _concurrent_upload( + signed_files: List[SignedFile], + upload_tasks: List[UploadTask], + max_workers: int = _DEFAULT_MAX_WORKERS, +) -> List[UploadResult]: + """Upload files concurrently (order-preserving, best-effort fail-fast). + + On the first upload failure, cancels futures not yet started. Futures already + running cannot be interrupted; the ThreadPoolExecutor context manager blocks + until they complete before this function returns. + """ + n = len(signed_files) + results: List[UploadResult | None] = [None] * n + + with ThreadPoolExecutor(max_workers=min(max_workers, n)) as executor: + future_to_idx: dict[Future[UploadResult], int] = {} + futures: list[Future[UploadResult]] = [] + for i, sf in enumerate(signed_files): + fut = executor.submit(s3_post_upload, sf) + futures.append(fut) + future_to_idx[fut] = i + + first_error: Exception | None = None + first_error_idx = -1 + + for fut in as_completed(future_to_idx): + idx = future_to_idx[fut] + try: + results[idx] = fut.result() + except Exception as exc: + first_error = exc + first_error_idx = idx + for other in futures: + if other is not fut: + other.cancel() + break + + if first_error is not None: + task = upload_tasks[first_error_idx] + raise MultimodalError( + f"Upload failed at messages[{task.msg_idx}].content[{task.content_idx}] " + f"(uri={task.uri}): {first_error}" + ) from first_error + + return results # type: ignore[return-value] + + +async def _async_concurrent_upload( + signed_files: List[SignedFile], + upload_tasks: List[UploadTask], +) -> List[UploadResult]: + """Async concurrent upload (order-preserving, fail-fast via FIRST_EXCEPTION). + + When FIRST_EXCEPTION fires, all tasks in `done` are already finished. + With no error, pending is always empty (asyncio.wait returns all in done). + """ + import asyncio + + n = len(signed_files) + results: List[UploadResult | None] = [None] * n + task_to_idx: dict[asyncio.Task[UploadResult], int] = {} + async_tasks: list[asyncio.Task[UploadResult]] = [] + + for i, sf in enumerate(signed_files): + t = asyncio.create_task(async_s3_post_upload(sf), name=f"upload-{i}") + async_tasks.append(t) + task_to_idx[t] = i + + done, pending = await asyncio.wait(async_tasks, return_when=asyncio.FIRST_EXCEPTION) + + first_error: BaseException | None = None + first_error_idx = -1 + + for t in done: + idx = task_to_idx[t] + exc = t.exception() + if exc is not None: + if first_error is None or idx < first_error_idx: + first_error = exc + first_error_idx = idx + else: + results[idx] = t.result() + + if first_error is not None: + for t in pending: + t.cancel() + if pending: + done_after_cancel, _ = await asyncio.wait(pending) + # Consume exceptions to suppress unobserved-exception warnings (Python 3.11+) + for t in done_after_cancel: + if not t.cancelled(): + t.exception() + task = upload_tasks[first_error_idx] + raise MultimodalError( + f"Upload failed at messages[{task.msg_idx}].content[{task.content_idx}]: {first_error}" + ) from first_error # type: ignore[arg-type] + + # No error: pending is always empty when FIRST_EXCEPTION returns all tasks done. + # (Defensive guard kept for clarity, but unreachable in practice.) + assert not pending, "unexpected pending tasks after FIRST_EXCEPTION with no error" + + return results # type: ignore[return-value] + + +def _replace_uris( + msg_list: list[dict[str, Any]], + upload_tasks: List[UploadTask], + results: List[UploadResult], +) -> None: + """Replace uri in each content item with the uploaded object_key.""" + for i, task in enumerate(upload_tasks): + result = results[i] + content_item = msg_list[task.msg_idx]["content"][task.content_idx] + log.debug( + "Replaced messages[%d].content[%d].uri: %s → %s", + task.msg_idx, task.content_idx, task.uri, result.object_key, + ) + content_item["uri"] = result.object_key + # auto-fill name and ext if absent + if not content_item.get("name") and result.filename: + content_item["name"] = result.filename + if not content_item.get("ext") and result.filename: + ext = os.path.splitext(result.filename)[1].lstrip(".") + if ext: + content_item["ext"] = ext + + +def _cleanup(resolved_files: List[ResolvedFile]) -> None: + """Clean up temp files. Always called from finally blocks.""" + cleaned = 0 + for r in resolved_files: + if r.is_temp: + r.cleanup() + cleaned += 1 + if cleaned: + log.debug("Cleaned up %d temp file(s)", cleaned) diff --git a/src/everos/lib/_upload.py b/src/everos/lib/_upload.py new file mode 100644 index 0000000..3d55a40 --- /dev/null +++ b/src/everos/lib/_upload.py @@ -0,0 +1,258 @@ +"""Batch sign + S3 POST form upload. + +One call to object.sign() signs all files; then each file is uploaded via S3 +presigned POST (multipart/form-data with policy fields + file). +""" + +from __future__ import annotations + +import time +import uuid +import asyncio +from typing import TYPE_CHECKING, List +from dataclasses import dataclass + +import httpx + +from ._files import ResolvedFile +from ._detect import UploadTask +from ._errors import UploadError + +if TYPE_CHECKING: + from .._client import EverOS, AsyncEverOS + +import logging + +log: logging.Logger = logging.getLogger("everos") + +_UPLOAD_TIMEOUT = 120.0 +_MAX_UPLOAD_RETRIES = 3 +_RETRY_BACKOFF_S = 1.0 # seconds to sleep between S3 upload retries + + +@dataclass +class SignedFile: + """Holds sign response data for a single file, ready for upload.""" + + resolved: ResolvedFile + task: UploadTask + object_key: str + upload_url: str + upload_fields: dict[str, str] + + +@dataclass +class UploadResult: + """Result of a single successful upload.""" + + object_key: str + filename: str + content_type: str + size: int + + +# ── Sync ────────────────────────────────────────────────────────────────────── + + +def batch_sign( + client: EverOS, + upload_tasks: List[UploadTask], + resolved_files: List[ResolvedFile], +) -> List[SignedFile]: + """Sign all files in a single API call. Sign uses SDK built-in retry.""" + object_list = [ + { + "file_id": f"sdk-{uuid.uuid4().hex}", + "file_name": resolved.filename, + "file_type": task.file_type, + } + for task, resolved in zip(upload_tasks, resolved_files) + ] + + log.debug("Signing %d file(s) in one batch request", len(object_list)) + sign_resp = client.v1.object.sign(object_list=object_list) # type: ignore[arg-type] + + if sign_resp.status != 0 or sign_resp.error != "OK": + raise UploadError( + f"Sign failed: status={sign_resp.status}, error={sign_resp.error}" + ) + + items = sign_resp.result.data.object_list if (sign_resp.result and sign_resp.result.data) else [] + if not items or len(items) != len(upload_tasks): + raise UploadError( + f"Sign returned {len(items or [])} items, expected {len(upload_tasks)}" + ) + + signed_files: list[SignedFile] = [] + for i, item in enumerate(items): + if not item.object_key or not item.object_signed_info or not item.object_signed_info.url: + raise UploadError(f"Sign response item {i} is missing object_key or upload_url") + log.debug(" Signed: %s → %s", resolved_files[i].filename, item.object_key) + signed_files.append(SignedFile( + resolved=resolved_files[i], + task=upload_tasks[i], + object_key=item.object_key, + upload_url=item.object_signed_info.url, + upload_fields=dict(item.object_signed_info.fields or {}), + )) + return signed_files + + +def s3_post_upload(signed: SignedFile) -> UploadResult: + """Upload a single file via S3 presigned POST form. Retries on 5xx / timeout.""" + resolved = signed.resolved + last_error: Exception | None = None + + for attempt in range(_MAX_UPLOAD_RETRIES): + try: + log.debug( + "Uploading %s (%s bytes) to S3 (attempt %d/%d)", + resolved.filename, resolved.size, attempt + 1, _MAX_UPLOAD_RETRIES, + ) + with resolved.open() as fh: + resp = httpx.post( + signed.upload_url, + data=signed.upload_fields, + files={"file": (resolved.filename, fh, resolved.content_type)}, + timeout=_UPLOAD_TIMEOUT, + ) + + if resp.status_code in (200, 201, 204): + log.debug("Uploaded %s → HTTP %d", resolved.filename, resp.status_code) + return UploadResult( + object_key=signed.object_key, + filename=resolved.filename, + content_type=resolved.content_type, + size=resolved.size, + ) + + if resp.status_code < 500: + raise UploadError( + f"S3 upload failed (HTTP {resp.status_code}): {resp.text[:400]}" + ) + + last_error = UploadError(f"S3 server error: HTTP {resp.status_code}") + log.warning( + "S3 upload retry %d/%d for %s: HTTP %d", + attempt + 1, _MAX_UPLOAD_RETRIES, resolved.filename, resp.status_code, + ) + + except httpx.TimeoutException as exc: + last_error = exc + log.warning( + "S3 upload retry %d/%d for %s: timeout", + attempt + 1, _MAX_UPLOAD_RETRIES, resolved.filename, + ) + + if attempt < _MAX_UPLOAD_RETRIES - 1: + time.sleep(_RETRY_BACKOFF_S) + + raise UploadError( + f"S3 upload failed after {_MAX_UPLOAD_RETRIES} attempts for {resolved.filename}" + ) from last_error + + +# ── Async ───────────────────────────────────────────────────────────────────── + + +async def async_batch_sign( + client: AsyncEverOS, + upload_tasks: List[UploadTask], + resolved_files: List[ResolvedFile], +) -> List[SignedFile]: + """Async batch sign — mirrors sync version.""" + object_list = [ + { + "file_id": f"sdk-{uuid.uuid4().hex}", + "file_name": resolved.filename, + "file_type": task.file_type, + } + for task, resolved in zip(upload_tasks, resolved_files) + ] + + log.debug("Signing %d file(s) in one batch request (async)", len(object_list)) + sign_resp = await client.v1.object.sign(object_list=object_list) # type: ignore[arg-type] + + if sign_resp.status != 0 or sign_resp.error != "OK": + raise UploadError( + f"Sign failed: status={sign_resp.status}, error={sign_resp.error}" + ) + + items = sign_resp.result.data.object_list if (sign_resp.result and sign_resp.result.data) else [] + if not items or len(items) != len(upload_tasks): + raise UploadError( + f"Sign returned {len(items or [])} items, expected {len(upload_tasks)}" + ) + + signed_files: list[SignedFile] = [] + for i, item in enumerate(items): + if not item.object_key or not item.object_signed_info or not item.object_signed_info.url: + raise UploadError(f"Sign response item {i} is missing object_key or upload_url") + log.debug(" Signed: %s → %s", resolved_files[i].filename, item.object_key) + signed_files.append(SignedFile( + resolved=resolved_files[i], + task=upload_tasks[i], + object_key=item.object_key, + upload_url=item.object_signed_info.url, + upload_fields=dict(item.object_signed_info.fields or {}), + )) + return signed_files + + +async def async_s3_post_upload(signed: SignedFile) -> UploadResult: + """Async S3 POST upload with retry.""" + import anyio + + resolved = signed.resolved + last_error: Exception | None = None + + for attempt in range(_MAX_UPLOAD_RETRIES): + try: + log.debug( + "Uploading %s (%s bytes) to S3 async (attempt %d/%d)", + resolved.filename, resolved.size, attempt + 1, _MAX_UPLOAD_RETRIES, + ) + fh = await anyio.to_thread.run_sync(resolved.open) # type: ignore[attr-defined] + try: + async with httpx.AsyncClient(timeout=_UPLOAD_TIMEOUT) as http: + resp = await http.post( + signed.upload_url, + data=signed.upload_fields, + files={"file": (resolved.filename, fh, resolved.content_type)}, # type: ignore[arg-type] + ) + finally: + await anyio.to_thread.run_sync(fh.close) # type: ignore[attr-defined] + + if resp.status_code in (200, 201, 204): + log.debug("Uploaded %s → HTTP %d", resolved.filename, resp.status_code) + return UploadResult( + object_key=signed.object_key, + filename=resolved.filename, + content_type=resolved.content_type, + size=resolved.size, + ) + + if resp.status_code < 500: + raise UploadError( + f"S3 upload failed (HTTP {resp.status_code}): {resp.text[:400]}" + ) + + last_error = UploadError(f"S3 server error: HTTP {resp.status_code}") + log.warning( + "S3 upload retry %d/%d for %s: HTTP %d", + attempt + 1, _MAX_UPLOAD_RETRIES, resolved.filename, resp.status_code, + ) + + except httpx.TimeoutException as exc: + last_error = exc + log.warning( + "S3 upload retry %d/%d for %s: timeout", + attempt + 1, _MAX_UPLOAD_RETRIES, resolved.filename, + ) + + if attempt < _MAX_UPLOAD_RETRIES - 1: + await asyncio.sleep(_RETRY_BACKOFF_S) + + raise UploadError( + f"S3 upload failed after {_MAX_UPLOAD_RETRIES} attempts for {resolved.filename}" + ) from last_error diff --git a/src/everos/resources/v1/__init__.py b/src/everos/resources/v1/__init__.py index 11564d9..447a6b8 100644 --- a/src/everos/resources/v1/__init__.py +++ b/src/everos/resources/v1/__init__.py @@ -8,6 +8,14 @@ V1ResourceWithStreamingResponse, AsyncV1ResourceWithStreamingResponse, ) +from .tasks import ( + TasksResource, + AsyncTasksResource, + TasksResourceWithRawResponse, + AsyncTasksResourceWithRawResponse, + TasksResourceWithStreamingResponse, + AsyncTasksResourceWithStreamingResponse, +) from .groups import ( GroupsResource, AsyncGroupsResource, @@ -50,36 +58,42 @@ ) __all__ = [ - "MemoriesResource", - "AsyncMemoriesResource", - "MemoriesResourceWithRawResponse", - "AsyncMemoriesResourceWithRawResponse", - "MemoriesResourceWithStreamingResponse", - "AsyncMemoriesResourceWithStreamingResponse", "GroupsResource", "AsyncGroupsResource", "GroupsResourceWithRawResponse", "AsyncGroupsResourceWithRawResponse", "GroupsResourceWithStreamingResponse", "AsyncGroupsResourceWithStreamingResponse", + "SettingsResource", + "AsyncSettingsResource", + "SettingsResourceWithRawResponse", + "AsyncSettingsResourceWithRawResponse", + "SettingsResourceWithStreamingResponse", + "AsyncSettingsResourceWithStreamingResponse", "SendersResource", "AsyncSendersResource", "SendersResourceWithRawResponse", "AsyncSendersResourceWithRawResponse", "SendersResourceWithStreamingResponse", "AsyncSendersResourceWithStreamingResponse", + "MemoriesResource", + "AsyncMemoriesResource", + "MemoriesResourceWithRawResponse", + "AsyncMemoriesResourceWithRawResponse", + "MemoriesResourceWithStreamingResponse", + "AsyncMemoriesResourceWithStreamingResponse", "ObjectResource", "AsyncObjectResource", "ObjectResourceWithRawResponse", "AsyncObjectResourceWithRawResponse", "ObjectResourceWithStreamingResponse", "AsyncObjectResourceWithStreamingResponse", - "SettingsResource", - "AsyncSettingsResource", - "SettingsResourceWithRawResponse", - "AsyncSettingsResourceWithRawResponse", - "SettingsResourceWithStreamingResponse", - "AsyncSettingsResourceWithStreamingResponse", + "TasksResource", + "AsyncTasksResource", + "TasksResourceWithRawResponse", + "AsyncTasksResourceWithRawResponse", + "TasksResourceWithStreamingResponse", + "AsyncTasksResourceWithStreamingResponse", "V1Resource", "AsyncV1Resource", "V1ResourceWithRawResponse", diff --git a/src/everos/resources/v1/groups.py b/src/everos/resources/v1/groups.py index 874d33d..37b9e96 100644 --- a/src/everos/resources/v1/groups.py +++ b/src/everos/resources/v1/groups.py @@ -9,7 +9,7 @@ from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property -from ...types.v1 import group_update_params, group_create_or_update_params +from ...types.v1 import group_patch_params, group_create_params from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -32,7 +32,7 @@ def with_raw_response(self) -> GroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return GroupsResourceWithRawResponse(self) @@ -41,14 +41,16 @@ def with_streaming_response(self) -> GroupsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return GroupsResourceWithStreamingResponse(self) - def retrieve( + def create( self, - group_id: str, *, + group_id: str, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -57,9 +59,15 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: """ - Retrieve a group's details by its group_id. + Create a new group or update an existing one (upsert by group_id). Args: + group_id: Group identifier (unique) + + description: Group description + + name: Group display name + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -68,22 +76,26 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not group_id: - raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") - return self._get( - path_template("/api/v1/groups/{group_id}", group_id=group_id), + return self._post( + "/api/v1/groups", + body=maybe_transform( + { + "group_id": group_id, + "description": description, + "name": name, + }, + group_create_params.GroupCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=GroupAPIResponse, ) - def update( + def retrieve( self, group_id: str, *, - description: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -91,16 +103,10 @@ def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: - """Partially update a group's fields. - - At least one of name or description must be - provided. + """ + Retrieve a group's details by its group_id. Args: - description: New group description - - name: New group display name - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -111,25 +117,18 @@ def update( """ if not group_id: raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") - return self._patch( + return self._get( path_template("/api/v1/groups/{group_id}", group_id=group_id), - body=maybe_transform( - { - "description": description, - "name": name, - }, - group_update_params.GroupUpdateParams, - ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=GroupAPIResponse, ) - def create_or_update( + def patch( self, - *, group_id: str, + *, description: Optional[str] | Omit = omit, name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -139,15 +138,15 @@ def create_or_update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: - """ - Create a new group or update an existing one (upsert by group_id). + """Partially update a group's fields. - Args: - group_id: Group identifier (unique) + At least one of name or description must be + provided. - description: Group description + Args: + description: New group description - name: Group display name + name: New group display name extra_headers: Send extra headers @@ -157,15 +156,16 @@ def create_or_update( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/api/v1/groups", + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return self._patch( + path_template("/api/v1/groups/{group_id}", group_id=group_id), body=maybe_transform( { - "group_id": group_id, "description": description, "name": name, }, - group_create_or_update_params.GroupCreateOrUpdateParams, + group_patch_params.GroupPatchParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -183,7 +183,7 @@ def with_raw_response(self) -> AsyncGroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncGroupsResourceWithRawResponse(self) @@ -192,14 +192,16 @@ def with_streaming_response(self) -> AsyncGroupsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncGroupsResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - group_id: str, *, + group_id: str, + description: Optional[str] | Omit = omit, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -208,9 +210,15 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: """ - Retrieve a group's details by its group_id. + Create a new group or update an existing one (upsert by group_id). Args: + group_id: Group identifier (unique) + + description: Group description + + name: Group display name + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -219,22 +227,26 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not group_id: - raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") - return await self._get( - path_template("/api/v1/groups/{group_id}", group_id=group_id), + return await self._post( + "/api/v1/groups", + body=await async_maybe_transform( + { + "group_id": group_id, + "description": description, + "name": name, + }, + group_create_params.GroupCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=GroupAPIResponse, ) - async def update( + async def retrieve( self, group_id: str, *, - description: Optional[str] | Omit = omit, - name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -242,16 +254,10 @@ async def update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: - """Partially update a group's fields. - - At least one of name or description must be - provided. + """ + Retrieve a group's details by its group_id. Args: - description: New group description - - name: New group display name - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -262,25 +268,18 @@ async def update( """ if not group_id: raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") - return await self._patch( + return await self._get( path_template("/api/v1/groups/{group_id}", group_id=group_id), - body=await async_maybe_transform( - { - "description": description, - "name": name, - }, - group_update_params.GroupUpdateParams, - ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=GroupAPIResponse, ) - async def create_or_update( + async def patch( self, - *, group_id: str, + *, description: Optional[str] | Omit = omit, name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -290,15 +289,15 @@ async def create_or_update( extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> GroupAPIResponse: - """ - Create a new group or update an existing one (upsert by group_id). + """Partially update a group's fields. - Args: - group_id: Group identifier (unique) + At least one of name or description must be + provided. - description: Group description + Args: + description: New group description - name: Group display name + name: New group display name extra_headers: Send extra headers @@ -308,15 +307,16 @@ async def create_or_update( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/api/v1/groups", + if not group_id: + raise ValueError(f"Expected a non-empty value for `group_id` but received {group_id!r}") + return await self._patch( + path_template("/api/v1/groups/{group_id}", group_id=group_id), body=await async_maybe_transform( { - "group_id": group_id, "description": description, "name": name, }, - group_create_or_update_params.GroupCreateOrUpdateParams, + group_patch_params.GroupPatchParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -329,14 +329,14 @@ class GroupsResourceWithRawResponse: def __init__(self, groups: GroupsResource) -> None: self._groups = groups + self.create = to_raw_response_wrapper( + groups.create, + ) self.retrieve = to_raw_response_wrapper( groups.retrieve, ) - self.update = to_raw_response_wrapper( - groups.update, - ) - self.create_or_update = to_raw_response_wrapper( - groups.create_or_update, + self.patch = to_raw_response_wrapper( + groups.patch, ) @@ -344,14 +344,14 @@ class AsyncGroupsResourceWithRawResponse: def __init__(self, groups: AsyncGroupsResource) -> None: self._groups = groups + self.create = async_to_raw_response_wrapper( + groups.create, + ) self.retrieve = async_to_raw_response_wrapper( groups.retrieve, ) - self.update = async_to_raw_response_wrapper( - groups.update, - ) - self.create_or_update = async_to_raw_response_wrapper( - groups.create_or_update, + self.patch = async_to_raw_response_wrapper( + groups.patch, ) @@ -359,14 +359,14 @@ class GroupsResourceWithStreamingResponse: def __init__(self, groups: GroupsResource) -> None: self._groups = groups + self.create = to_streamed_response_wrapper( + groups.create, + ) self.retrieve = to_streamed_response_wrapper( groups.retrieve, ) - self.update = to_streamed_response_wrapper( - groups.update, - ) - self.create_or_update = to_streamed_response_wrapper( - groups.create_or_update, + self.patch = to_streamed_response_wrapper( + groups.patch, ) @@ -374,12 +374,12 @@ class AsyncGroupsResourceWithStreamingResponse: def __init__(self, groups: AsyncGroupsResource) -> None: self._groups = groups + self.create = async_to_streamed_response_wrapper( + groups.create, + ) self.retrieve = async_to_streamed_response_wrapper( groups.retrieve, ) - self.update = async_to_streamed_response_wrapper( - groups.update, - ) - self.create_or_update = async_to_streamed_response_wrapper( - groups.create_or_update, + self.patch = async_to_streamed_response_wrapper( + groups.patch, ) diff --git a/src/everos/resources/v1/memories/__init__.py b/src/everos/resources/v1/memories/__init__.py index 5642e4a..1e620a4 100644 --- a/src/everos/resources/v1/memories/__init__.py +++ b/src/everos/resources/v1/memories/__init__.py @@ -26,18 +26,18 @@ ) __all__ = [ - "GroupResource", - "AsyncGroupResource", - "GroupResourceWithRawResponse", - "AsyncGroupResourceWithRawResponse", - "GroupResourceWithStreamingResponse", - "AsyncGroupResourceWithStreamingResponse", "AgentResource", "AsyncAgentResource", "AgentResourceWithRawResponse", "AsyncAgentResourceWithRawResponse", "AgentResourceWithStreamingResponse", "AsyncAgentResourceWithStreamingResponse", + "GroupResource", + "AsyncGroupResource", + "GroupResourceWithRawResponse", + "AsyncGroupResourceWithRawResponse", + "GroupResourceWithStreamingResponse", + "AsyncGroupResourceWithStreamingResponse", "MemoriesResource", "AsyncMemoriesResource", "MemoriesResourceWithRawResponse", diff --git a/src/everos/resources/v1/memories/agent.py b/src/everos/resources/v1/memories/agent.py index cf20da1..db0b068 100644 --- a/src/everos/resources/v1/memories/agent.py +++ b/src/everos/resources/v1/memories/agent.py @@ -17,9 +17,10 @@ async_to_streamed_response_wrapper, ) from ...._base_client import make_request_options -from ....types.v1.memories import agent_flush_params, agent_create_params +from ....types.v1.memories import agent_add_params, agent_flush_params from ....types.v1.add_response import AddResponse from ....types.v1.flush_response import FlushResponse +from ....types.v1.memories.agent_message_item_param import AgentMessageItemParam __all__ = ["AgentResource", "AsyncAgentResource"] @@ -33,7 +34,7 @@ def with_raw_response(self) -> AgentResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AgentResourceWithRawResponse(self) @@ -42,14 +43,14 @@ def with_streaming_response(self) -> AgentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AgentResourceWithStreamingResponse(self) - def create( + def add( self, *, - messages: Iterable[agent_create_params.Message], + messages: Iterable[AgentMessageItemParam], user_id: str, async_mode: bool | Omit = omit, session_id: Optional[str] | Omit = omit, @@ -92,7 +93,7 @@ def create( "async_mode": async_mode, "session_id": session_id, }, - agent_create_params.AgentCreateParams, + agent_add_params.AgentAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -154,7 +155,7 @@ def with_raw_response(self) -> AsyncAgentResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncAgentResourceWithRawResponse(self) @@ -163,14 +164,14 @@ def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncAgentResourceWithStreamingResponse(self) - async def create( + async def add( self, *, - messages: Iterable[agent_create_params.Message], + messages: Iterable[AgentMessageItemParam], user_id: str, async_mode: bool | Omit = omit, session_id: Optional[str] | Omit = omit, @@ -213,7 +214,7 @@ async def create( "async_mode": async_mode, "session_id": session_id, }, - agent_create_params.AgentCreateParams, + agent_add_params.AgentAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -270,8 +271,8 @@ class AgentResourceWithRawResponse: def __init__(self, agent: AgentResource) -> None: self._agent = agent - self.create = to_raw_response_wrapper( - agent.create, + self.add = to_raw_response_wrapper( + agent.add, ) self.flush = to_raw_response_wrapper( agent.flush, @@ -282,8 +283,8 @@ class AsyncAgentResourceWithRawResponse: def __init__(self, agent: AsyncAgentResource) -> None: self._agent = agent - self.create = async_to_raw_response_wrapper( - agent.create, + self.add = async_to_raw_response_wrapper( + agent.add, ) self.flush = async_to_raw_response_wrapper( agent.flush, @@ -294,8 +295,8 @@ class AgentResourceWithStreamingResponse: def __init__(self, agent: AgentResource) -> None: self._agent = agent - self.create = to_streamed_response_wrapper( - agent.create, + self.add = to_streamed_response_wrapper( + agent.add, ) self.flush = to_streamed_response_wrapper( agent.flush, @@ -306,8 +307,8 @@ class AsyncAgentResourceWithStreamingResponse: def __init__(self, agent: AsyncAgentResource) -> None: self._agent = agent - self.create = async_to_streamed_response_wrapper( - agent.create, + self.add = async_to_streamed_response_wrapper( + agent.add, ) self.flush = async_to_streamed_response_wrapper( agent.flush, diff --git a/src/everos/resources/v1/memories/group.py b/src/everos/resources/v1/memories/group.py index 3643608..7492827 100644 --- a/src/everos/resources/v1/memories/group.py +++ b/src/everos/resources/v1/memories/group.py @@ -17,9 +17,10 @@ async_to_streamed_response_wrapper, ) from ...._base_client import make_request_options -from ....types.v1.memories import group_flush_params, group_create_params +from ....types.v1.memories import group_add_params, group_flush_params from ....types.v1.add_response import AddResponse from ....types.v1.flush_response import FlushResponse +from ....types.v1.memories.group_message_item_param import GroupMessageItemParam __all__ = ["GroupResource", "AsyncGroupResource"] @@ -33,7 +34,7 @@ def with_raw_response(self) -> GroupResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return GroupResourceWithRawResponse(self) @@ -42,15 +43,15 @@ def with_streaming_response(self) -> GroupResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return GroupResourceWithStreamingResponse(self) - def create( + def add( self, *, group_id: str, - messages: Iterable[group_create_params.Message], + messages: Iterable[GroupMessageItemParam], async_mode: bool | Omit = omit, group_meta: Optional[Dict[str, object]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -92,7 +93,7 @@ def create( "async_mode": async_mode, "group_meta": group_meta, }, - group_create_params.GroupCreateParams, + group_add_params.GroupAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -144,7 +145,7 @@ def with_raw_response(self) -> AsyncGroupResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncGroupResourceWithRawResponse(self) @@ -153,15 +154,15 @@ def with_streaming_response(self) -> AsyncGroupResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncGroupResourceWithStreamingResponse(self) - async def create( + async def add( self, *, group_id: str, - messages: Iterable[group_create_params.Message], + messages: Iterable[GroupMessageItemParam], async_mode: bool | Omit = omit, group_meta: Optional[Dict[str, object]] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -203,7 +204,7 @@ async def create( "async_mode": async_mode, "group_meta": group_meta, }, - group_create_params.GroupCreateParams, + group_add_params.GroupAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout @@ -250,8 +251,8 @@ class GroupResourceWithRawResponse: def __init__(self, group: GroupResource) -> None: self._group = group - self.create = to_raw_response_wrapper( - group.create, + self.add = to_raw_response_wrapper( + group.add, ) self.flush = to_raw_response_wrapper( group.flush, @@ -262,8 +263,8 @@ class AsyncGroupResourceWithRawResponse: def __init__(self, group: AsyncGroupResource) -> None: self._group = group - self.create = async_to_raw_response_wrapper( - group.create, + self.add = async_to_raw_response_wrapper( + group.add, ) self.flush = async_to_raw_response_wrapper( group.flush, @@ -274,8 +275,8 @@ class GroupResourceWithStreamingResponse: def __init__(self, group: GroupResource) -> None: self._group = group - self.create = to_streamed_response_wrapper( - group.create, + self.add = to_streamed_response_wrapper( + group.add, ) self.flush = to_streamed_response_wrapper( group.flush, @@ -286,8 +287,8 @@ class AsyncGroupResourceWithStreamingResponse: def __init__(self, group: AsyncGroupResource) -> None: self._group = group - self.create = async_to_streamed_response_wrapper( - group.create, + self.add = async_to_streamed_response_wrapper( + group.add, ) self.flush = async_to_streamed_response_wrapper( group.flush, diff --git a/src/everos/resources/v1/memories/memories.py b/src/everos/resources/v1/memories/memories.py index 340bfc6..a362fd5 100644 --- a/src/everos/resources/v1/memories/memories.py +++ b/src/everos/resources/v1/memories/memories.py @@ -27,9 +27,9 @@ from ...._utils import maybe_transform, async_maybe_transform from ...._compat import cached_property from ....types.v1 import ( + memory_add_params, memory_get_params, memory_flush_params, - memory_create_params, memory_delete_params, memory_search_params, ) @@ -43,8 +43,9 @@ from ...._base_client import make_request_options from ....types.v1.add_response import AddResponse from ....types.v1.flush_response import FlushResponse -from ....types.v1.memory_get_response import MemoryGetResponse -from ....types.v1.memory_search_response import MemorySearchResponse +from ....types.v1.message_item_param import MessageItemParam +from ....types.v1.get_memories_response import GetMemoriesResponse +from ....types.v1.search_memories_response import SearchMemoriesResponse __all__ = ["MemoriesResource", "AsyncMemoriesResource"] @@ -53,14 +54,14 @@ class MemoriesResource(SyncAPIResource): """Memory ingestion, retrieval, search, and deletion""" @cached_property - def group(self) -> GroupResource: + def agent(self) -> AgentResource: """Memory ingestion, retrieval, search, and deletion""" - return GroupResource(self._client) + return AgentResource(self._client) @cached_property - def agent(self) -> AgentResource: + def group(self) -> GroupResource: """Memory ingestion, retrieval, search, and deletion""" - return AgentResource(self._client) + return GroupResource(self._client) @cached_property def with_raw_response(self) -> MemoriesResourceWithRawResponse: @@ -68,7 +69,7 @@ def with_raw_response(self) -> MemoriesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return MemoriesResourceWithRawResponse(self) @@ -77,38 +78,47 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return MemoriesResourceWithStreamingResponse(self) - def create( + def delete( self, *, - messages: Iterable[memory_create_params.Message], - user_id: str, - async_mode: bool | Omit = omit, + group_id: Optional[str] | Omit = omit, + memory_id: Optional[str] | Omit = omit, + sender_id: Optional[str] | Omit = omit, session_id: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AddResponse: - """Store batch messages into personal memory space. + ) -> None: + """ + Soft delete memories by ID or by filter conditions. - Messages are accumulated and - boundary detection triggers memory extraction automatically. + Two mutually exclusive modes: + + - **By ID**: provide `memory_id` only (no other fields allowed) + - **By filters**: provide `user_id` and/or `group_id` (session_id, sender_id + optional) + + Filter semantics: omitted fields skip filtering; `null` or `""` matches + null/empty records; string value = exact match. Args: - messages: Batch message array (1-500 items) + group_id: Group ID scope for batch delete - user_id: Owner user ID + memory_id: MemCell ID for single delete - async_mode: Enable async processing. When true, returns 202 with task_id; when false, - processes synchronously and returns 200. + sender_id: Sender filter, matches participants array (batch delete only) - session_id: Session identifier + session_id: Session filter (batch delete only) + + user_id: User ID scope for batch delete extra_headers: Send extra headers @@ -118,60 +128,53 @@ def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._post( - "/api/v1/memories", + "/api/v1/memories/delete", body=maybe_transform( { - "messages": messages, - "user_id": user_id, - "async_mode": async_mode, + "group_id": group_id, + "memory_id": memory_id, + "sender_id": sender_id, "session_id": session_id, + "user_id": user_id, }, - memory_create_params.MemoryCreateParams, + memory_delete_params.MemoryDeleteParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AddResponse, + cast_to=NoneType, ) - def delete( + def add( self, *, - group_id: Optional[str] | Omit = omit, - memory_id: Optional[str] | Omit = omit, - sender_id: Optional[str] | Omit = omit, + messages: Iterable[MessageItemParam], + user_id: str, + async_mode: bool | Omit = omit, session_id: Optional[str] | Omit = omit, - user_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Soft delete memories by ID or by filter conditions. - - Two mutually exclusive modes: - - - **By ID**: provide `memory_id` only (no other fields allowed) - - **By filters**: provide `user_id` and/or `group_id` (session_id, sender_id - optional) + ) -> AddResponse: + """Store batch messages into personal memory space. - Filter semantics: omitted fields skip filtering; `null` or `""` matches - null/empty records; string value = exact match. + Messages are accumulated and + boundary detection triggers memory extraction automatically. Args: - group_id: Group ID scope for batch delete - - memory_id: MemCell ID for single delete + messages: Batch message array (1-500 items) - sender_id: Sender filter, matches participants array (batch delete only) + user_id: Owner user ID - session_id: Session filter (batch delete only) + async_mode: Enable async processing. When true, returns 202 with task_id; when false, + processes synchronously and returns 200. - user_id: User ID scope for batch delete + session_id: Session identifier extra_headers: Send extra headers @@ -181,23 +184,21 @@ def delete( timeout: Override the client-level default timeout for this request, in seconds """ - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return self._post( - "/api/v1/memories/delete", + "/api/v1/memories", body=maybe_transform( { - "group_id": group_id, - "memory_id": memory_id, - "sender_id": sender_id, - "session_id": session_id, + "messages": messages, "user_id": user_id, + "async_mode": async_mode, + "session_id": session_id, }, - memory_delete_params.MemoryDeleteParams, + memory_add_params.MemoryAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=AddResponse, ) def flush( @@ -260,7 +261,7 @@ def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> MemoryGetResponse: + ) -> GetMemoriesResponse: """Retrieve structured memories using filters DSL with pagination. Supports @@ -327,7 +328,7 @@ def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryGetResponse, + cast_to=GetMemoriesResponse, ) def search( @@ -346,7 +347,7 @@ def search( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> MemorySearchResponse: + ) -> SearchMemoriesResponse: """ Unified memory search endpoint supporting multiple memory types and retrieval methods. @@ -412,7 +413,7 @@ def search( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemorySearchResponse, + cast_to=SearchMemoriesResponse, ) @@ -420,14 +421,14 @@ class AsyncMemoriesResource(AsyncAPIResource): """Memory ingestion, retrieval, search, and deletion""" @cached_property - def group(self) -> AsyncGroupResource: + def agent(self) -> AsyncAgentResource: """Memory ingestion, retrieval, search, and deletion""" - return AsyncGroupResource(self._client) + return AsyncAgentResource(self._client) @cached_property - def agent(self) -> AsyncAgentResource: + def group(self) -> AsyncGroupResource: """Memory ingestion, retrieval, search, and deletion""" - return AsyncAgentResource(self._client) + return AsyncGroupResource(self._client) @cached_property def with_raw_response(self) -> AsyncMemoriesResourceWithRawResponse: @@ -435,7 +436,7 @@ def with_raw_response(self) -> AsyncMemoriesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncMemoriesResourceWithRawResponse(self) @@ -444,38 +445,47 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncMemoriesResourceWithStreamingResponse(self) - async def create( + async def delete( self, *, - messages: Iterable[memory_create_params.Message], - user_id: str, - async_mode: bool | Omit = omit, + group_id: Optional[str] | Omit = omit, + memory_id: Optional[str] | Omit = omit, + sender_id: Optional[str] | Omit = omit, session_id: Optional[str] | Omit = omit, + user_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> AddResponse: - """Store batch messages into personal memory space. + ) -> None: + """ + Soft delete memories by ID or by filter conditions. - Messages are accumulated and - boundary detection triggers memory extraction automatically. + Two mutually exclusive modes: + + - **By ID**: provide `memory_id` only (no other fields allowed) + - **By filters**: provide `user_id` and/or `group_id` (session_id, sender_id + optional) + + Filter semantics: omitted fields skip filtering; `null` or `""` matches + null/empty records; string value = exact match. Args: - messages: Batch message array (1-500 items) + group_id: Group ID scope for batch delete - user_id: Owner user ID + memory_id: MemCell ID for single delete - async_mode: Enable async processing. When true, returns 202 with task_id; when false, - processes synchronously and returns 200. + sender_id: Sender filter, matches participants array (batch delete only) - session_id: Session identifier + session_id: Session filter (batch delete only) + + user_id: User ID scope for batch delete extra_headers: Send extra headers @@ -485,60 +495,53 @@ async def create( timeout: Override the client-level default timeout for this request, in seconds """ + extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._post( - "/api/v1/memories", + "/api/v1/memories/delete", body=await async_maybe_transform( { - "messages": messages, - "user_id": user_id, - "async_mode": async_mode, + "group_id": group_id, + "memory_id": memory_id, + "sender_id": sender_id, "session_id": session_id, + "user_id": user_id, }, - memory_create_params.MemoryCreateParams, + memory_delete_params.MemoryDeleteParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=AddResponse, + cast_to=NoneType, ) - async def delete( + async def add( self, *, - group_id: Optional[str] | Omit = omit, - memory_id: Optional[str] | Omit = omit, - sender_id: Optional[str] | Omit = omit, + messages: Iterable[MessageItemParam], + user_id: str, + async_mode: bool | Omit = omit, session_id: Optional[str] | Omit = omit, - user_id: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> None: - """ - Soft delete memories by ID or by filter conditions. - - Two mutually exclusive modes: - - - **By ID**: provide `memory_id` only (no other fields allowed) - - **By filters**: provide `user_id` and/or `group_id` (session_id, sender_id - optional) + ) -> AddResponse: + """Store batch messages into personal memory space. - Filter semantics: omitted fields skip filtering; `null` or `""` matches - null/empty records; string value = exact match. + Messages are accumulated and + boundary detection triggers memory extraction automatically. Args: - group_id: Group ID scope for batch delete - - memory_id: MemCell ID for single delete + messages: Batch message array (1-500 items) - sender_id: Sender filter, matches participants array (batch delete only) + user_id: Owner user ID - session_id: Session filter (batch delete only) + async_mode: Enable async processing. When true, returns 202 with task_id; when false, + processes synchronously and returns 200. - user_id: User ID scope for batch delete + session_id: Session identifier extra_headers: Send extra headers @@ -548,23 +551,21 @@ async def delete( timeout: Override the client-level default timeout for this request, in seconds """ - extra_headers = {"Accept": "*/*", **(extra_headers or {})} return await self._post( - "/api/v1/memories/delete", + "/api/v1/memories", body=await async_maybe_transform( { - "group_id": group_id, - "memory_id": memory_id, - "sender_id": sender_id, - "session_id": session_id, + "messages": messages, "user_id": user_id, + "async_mode": async_mode, + "session_id": session_id, }, - memory_delete_params.MemoryDeleteParams, + memory_add_params.MemoryAddParams, ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=NoneType, + cast_to=AddResponse, ) async def flush( @@ -627,7 +628,7 @@ async def get( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> MemoryGetResponse: + ) -> GetMemoriesResponse: """Retrieve structured memories using filters DSL with pagination. Supports @@ -694,7 +695,7 @@ async def get( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemoryGetResponse, + cast_to=GetMemoriesResponse, ) async def search( @@ -713,7 +714,7 @@ async def search( extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> MemorySearchResponse: + ) -> SearchMemoriesResponse: """ Unified memory search endpoint supporting multiple memory types and retrieval methods. @@ -779,7 +780,7 @@ async def search( options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=MemorySearchResponse, + cast_to=SearchMemoriesResponse, ) @@ -787,12 +788,12 @@ class MemoriesResourceWithRawResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories - self.create = to_raw_response_wrapper( - memories.create, - ) self.delete = to_raw_response_wrapper( memories.delete, ) + self.add = to_raw_response_wrapper( + memories.add, + ) self.flush = to_raw_response_wrapper( memories.flush, ) @@ -804,26 +805,26 @@ def __init__(self, memories: MemoriesResource) -> None: ) @cached_property - def group(self) -> GroupResourceWithRawResponse: + def agent(self) -> AgentResourceWithRawResponse: """Memory ingestion, retrieval, search, and deletion""" - return GroupResourceWithRawResponse(self._memories.group) + return AgentResourceWithRawResponse(self._memories.agent) @cached_property - def agent(self) -> AgentResourceWithRawResponse: + def group(self) -> GroupResourceWithRawResponse: """Memory ingestion, retrieval, search, and deletion""" - return AgentResourceWithRawResponse(self._memories.agent) + return GroupResourceWithRawResponse(self._memories.group) class AsyncMemoriesResourceWithRawResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories - self.create = async_to_raw_response_wrapper( - memories.create, - ) self.delete = async_to_raw_response_wrapper( memories.delete, ) + self.add = async_to_raw_response_wrapper( + memories.add, + ) self.flush = async_to_raw_response_wrapper( memories.flush, ) @@ -835,26 +836,26 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: ) @cached_property - def group(self) -> AsyncGroupResourceWithRawResponse: + def agent(self) -> AsyncAgentResourceWithRawResponse: """Memory ingestion, retrieval, search, and deletion""" - return AsyncGroupResourceWithRawResponse(self._memories.group) + return AsyncAgentResourceWithRawResponse(self._memories.agent) @cached_property - def agent(self) -> AsyncAgentResourceWithRawResponse: + def group(self) -> AsyncGroupResourceWithRawResponse: """Memory ingestion, retrieval, search, and deletion""" - return AsyncAgentResourceWithRawResponse(self._memories.agent) + return AsyncGroupResourceWithRawResponse(self._memories.group) class MemoriesResourceWithStreamingResponse: def __init__(self, memories: MemoriesResource) -> None: self._memories = memories - self.create = to_streamed_response_wrapper( - memories.create, - ) self.delete = to_streamed_response_wrapper( memories.delete, ) + self.add = to_streamed_response_wrapper( + memories.add, + ) self.flush = to_streamed_response_wrapper( memories.flush, ) @@ -866,26 +867,26 @@ def __init__(self, memories: MemoriesResource) -> None: ) @cached_property - def group(self) -> GroupResourceWithStreamingResponse: + def agent(self) -> AgentResourceWithStreamingResponse: """Memory ingestion, retrieval, search, and deletion""" - return GroupResourceWithStreamingResponse(self._memories.group) + return AgentResourceWithStreamingResponse(self._memories.agent) @cached_property - def agent(self) -> AgentResourceWithStreamingResponse: + def group(self) -> GroupResourceWithStreamingResponse: """Memory ingestion, retrieval, search, and deletion""" - return AgentResourceWithStreamingResponse(self._memories.agent) + return GroupResourceWithStreamingResponse(self._memories.group) class AsyncMemoriesResourceWithStreamingResponse: def __init__(self, memories: AsyncMemoriesResource) -> None: self._memories = memories - self.create = async_to_streamed_response_wrapper( - memories.create, - ) self.delete = async_to_streamed_response_wrapper( memories.delete, ) + self.add = async_to_streamed_response_wrapper( + memories.add, + ) self.flush = async_to_streamed_response_wrapper( memories.flush, ) @@ -897,11 +898,11 @@ def __init__(self, memories: AsyncMemoriesResource) -> None: ) @cached_property - def group(self) -> AsyncGroupResourceWithStreamingResponse: + def agent(self) -> AsyncAgentResourceWithStreamingResponse: """Memory ingestion, retrieval, search, and deletion""" - return AsyncGroupResourceWithStreamingResponse(self._memories.group) + return AsyncAgentResourceWithStreamingResponse(self._memories.agent) @cached_property - def agent(self) -> AsyncAgentResourceWithStreamingResponse: + def group(self) -> AsyncGroupResourceWithStreamingResponse: """Memory ingestion, retrieval, search, and deletion""" - return AsyncAgentResourceWithStreamingResponse(self._memories.agent) + return AsyncGroupResourceWithStreamingResponse(self._memories.group) diff --git a/src/everos/resources/v1/object.py b/src/everos/resources/v1/object.py index a4dc96a..a665968 100644 --- a/src/everos/resources/v1/object.py +++ b/src/everos/resources/v1/object.py @@ -9,7 +9,7 @@ from ..._types import Body, Query, Headers, NotGiven, not_given from ..._utils import maybe_transform, async_maybe_transform from ..._compat import cached_property -from ...types.v1 import object_get_presigned_url_params +from ...types.v1 import object_sign_params from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -18,7 +18,8 @@ async_to_streamed_response_wrapper, ) from ..._base_client import make_request_options -from ...types.v1.object_get_presigned_url_response import ObjectGetPresignedURLResponse +from ...types.v1.object_sign_response import ObjectSignResponse +from ...types.v1.object_sign_item_request_param import ObjectSignItemRequestParam __all__ = ["ObjectResource", "AsyncObjectResource"] @@ -32,7 +33,7 @@ def with_raw_response(self) -> ObjectResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return ObjectResourceWithRawResponse(self) @@ -41,21 +42,21 @@ def with_streaming_response(self) -> ObjectResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return ObjectResourceWithStreamingResponse(self) - def get_presigned_url( + def sign( self, *, - object_list: Iterable[object_get_presigned_url_params.ObjectList], + object_list: Iterable[ObjectSignItemRequestParam], # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ObjectGetPresignedURLResponse: + ) -> ObjectSignResponse: """Generate a pre-signed S3 upload URL for multimodal content. Gateway @@ -79,13 +80,11 @@ def get_presigned_url( """ return self._post( "/api/v1/object/sign", - body=maybe_transform( - {"object_list": object_list}, object_get_presigned_url_params.ObjectGetPresignedURLParams - ), + body=maybe_transform({"object_list": object_list}, object_sign_params.ObjectSignParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ObjectGetPresignedURLResponse, + cast_to=ObjectSignResponse, ) @@ -98,7 +97,7 @@ def with_raw_response(self) -> AsyncObjectResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncObjectResourceWithRawResponse(self) @@ -107,21 +106,21 @@ def with_streaming_response(self) -> AsyncObjectResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncObjectResourceWithStreamingResponse(self) - async def get_presigned_url( + async def sign( self, *, - object_list: Iterable[object_get_presigned_url_params.ObjectList], + object_list: Iterable[ObjectSignItemRequestParam], # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, extra_query: Query | None = None, extra_body: Body | None = None, timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> ObjectGetPresignedURLResponse: + ) -> ObjectSignResponse: """Generate a pre-signed S3 upload URL for multimodal content. Gateway @@ -145,13 +144,11 @@ async def get_presigned_url( """ return await self._post( "/api/v1/object/sign", - body=await async_maybe_transform( - {"object_list": object_list}, object_get_presigned_url_params.ObjectGetPresignedURLParams - ), + body=await async_maybe_transform({"object_list": object_list}, object_sign_params.ObjectSignParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), - cast_to=ObjectGetPresignedURLResponse, + cast_to=ObjectSignResponse, ) @@ -159,8 +156,8 @@ class ObjectResourceWithRawResponse: def __init__(self, object: ObjectResource) -> None: self._object = object - self.get_presigned_url = to_raw_response_wrapper( - object.get_presigned_url, + self.sign = to_raw_response_wrapper( + object.sign, ) @@ -168,8 +165,8 @@ class AsyncObjectResourceWithRawResponse: def __init__(self, object: AsyncObjectResource) -> None: self._object = object - self.get_presigned_url = async_to_raw_response_wrapper( - object.get_presigned_url, + self.sign = async_to_raw_response_wrapper( + object.sign, ) @@ -177,8 +174,8 @@ class ObjectResourceWithStreamingResponse: def __init__(self, object: ObjectResource) -> None: self._object = object - self.get_presigned_url = to_streamed_response_wrapper( - object.get_presigned_url, + self.sign = to_streamed_response_wrapper( + object.sign, ) @@ -186,6 +183,6 @@ class AsyncObjectResourceWithStreamingResponse: def __init__(self, object: AsyncObjectResource) -> None: self._object = object - self.get_presigned_url = async_to_streamed_response_wrapper( - object.get_presigned_url, + self.sign = async_to_streamed_response_wrapper( + object.sign, ) diff --git a/src/everos/resources/v1/senders.py b/src/everos/resources/v1/senders.py index f4449c2..74bc47c 100644 --- a/src/everos/resources/v1/senders.py +++ b/src/everos/resources/v1/senders.py @@ -9,7 +9,7 @@ from ..._types import Body, Omit, Query, Headers, NotGiven, omit, not_given from ..._utils import path_template, maybe_transform, async_maybe_transform from ..._compat import cached_property -from ...types.v1 import sender_update_params, sender_create_or_update_params +from ...types.v1 import sender_patch_params, sender_create_params from ..._resource import SyncAPIResource, AsyncAPIResource from ..._response import ( to_raw_response_wrapper, @@ -32,7 +32,7 @@ def with_raw_response(self) -> SendersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return SendersResourceWithRawResponse(self) @@ -41,14 +41,15 @@ def with_streaming_response(self) -> SendersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return SendersResourceWithStreamingResponse(self) - def retrieve( + def create( self, - sender_id: str, *, + sender_id: str, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -57,9 +58,13 @@ def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Retrieve a sender's details by its sender_id. + Create a new sender or update an existing one (upsert by sender_id). Args: + sender_id: Sender identifier (unique) + + name: Sender display name + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -68,21 +73,25 @@ def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not sender_id: - raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") - return self._get( - path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), + return self._post( + "/api/v1/senders", + body=maybe_transform( + { + "sender_id": sender_id, + "name": name, + }, + sender_create_params.SenderCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SenderAPIResponse, ) - def update( + def retrieve( self, sender_id: str, *, - name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -91,11 +100,9 @@ def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Update a sender's display name. + Retrieve a sender's details by its sender_id. Args: - name: New sender display name - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -106,19 +113,18 @@ def update( """ if not sender_id: raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") - return self._patch( + return self._get( path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), - body=maybe_transform({"name": name}, sender_update_params.SenderUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SenderAPIResponse, ) - def create_or_update( + def patch( self, - *, sender_id: str, + *, name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -128,12 +134,10 @@ def create_or_update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Create a new sender or update an existing one (upsert by sender_id). + Update a sender's display name. Args: - sender_id: Sender identifier (unique) - - name: Sender display name + name: New sender display name extra_headers: Send extra headers @@ -143,15 +147,11 @@ def create_or_update( timeout: Override the client-level default timeout for this request, in seconds """ - return self._post( - "/api/v1/senders", - body=maybe_transform( - { - "sender_id": sender_id, - "name": name, - }, - sender_create_or_update_params.SenderCreateOrUpdateParams, - ), + if not sender_id: + raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") + return self._patch( + path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), + body=maybe_transform({"name": name}, sender_patch_params.SenderPatchParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -168,7 +168,7 @@ def with_raw_response(self) -> AsyncSendersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncSendersResourceWithRawResponse(self) @@ -177,14 +177,15 @@ def with_streaming_response(self) -> AsyncSendersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncSendersResourceWithStreamingResponse(self) - async def retrieve( + async def create( self, - sender_id: str, *, + sender_id: str, + name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -193,9 +194,13 @@ async def retrieve( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Retrieve a sender's details by its sender_id. + Create a new sender or update an existing one (upsert by sender_id). Args: + sender_id: Sender identifier (unique) + + name: Sender display name + extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -204,21 +209,25 @@ async def retrieve( timeout: Override the client-level default timeout for this request, in seconds """ - if not sender_id: - raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") - return await self._get( - path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), + return await self._post( + "/api/v1/senders", + body=await async_maybe_transform( + { + "sender_id": sender_id, + "name": name, + }, + sender_create_params.SenderCreateParams, + ), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SenderAPIResponse, ) - async def update( + async def retrieve( self, sender_id: str, *, - name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. extra_headers: Headers | None = None, @@ -227,11 +236,9 @@ async def update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Update a sender's display name. + Retrieve a sender's details by its sender_id. Args: - name: New sender display name - extra_headers: Send extra headers extra_query: Add additional query parameters to the request @@ -242,19 +249,18 @@ async def update( """ if not sender_id: raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") - return await self._patch( + return await self._get( path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), - body=await async_maybe_transform({"name": name}, sender_update_params.SenderUpdateParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), cast_to=SenderAPIResponse, ) - async def create_or_update( + async def patch( self, - *, sender_id: str, + *, name: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. # The extra values given here take precedence over values defined on the client or passed to this method. @@ -264,12 +270,10 @@ async def create_or_update( timeout: float | httpx.Timeout | None | NotGiven = not_given, ) -> SenderAPIResponse: """ - Create a new sender or update an existing one (upsert by sender_id). + Update a sender's display name. Args: - sender_id: Sender identifier (unique) - - name: Sender display name + name: New sender display name extra_headers: Send extra headers @@ -279,15 +283,11 @@ async def create_or_update( timeout: Override the client-level default timeout for this request, in seconds """ - return await self._post( - "/api/v1/senders", - body=await async_maybe_transform( - { - "sender_id": sender_id, - "name": name, - }, - sender_create_or_update_params.SenderCreateOrUpdateParams, - ), + if not sender_id: + raise ValueError(f"Expected a non-empty value for `sender_id` but received {sender_id!r}") + return await self._patch( + path_template("/api/v1/senders/{sender_id}", sender_id=sender_id), + body=await async_maybe_transform({"name": name}, sender_patch_params.SenderPatchParams), options=make_request_options( extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout ), @@ -299,14 +299,14 @@ class SendersResourceWithRawResponse: def __init__(self, senders: SendersResource) -> None: self._senders = senders + self.create = to_raw_response_wrapper( + senders.create, + ) self.retrieve = to_raw_response_wrapper( senders.retrieve, ) - self.update = to_raw_response_wrapper( - senders.update, - ) - self.create_or_update = to_raw_response_wrapper( - senders.create_or_update, + self.patch = to_raw_response_wrapper( + senders.patch, ) @@ -314,14 +314,14 @@ class AsyncSendersResourceWithRawResponse: def __init__(self, senders: AsyncSendersResource) -> None: self._senders = senders + self.create = async_to_raw_response_wrapper( + senders.create, + ) self.retrieve = async_to_raw_response_wrapper( senders.retrieve, ) - self.update = async_to_raw_response_wrapper( - senders.update, - ) - self.create_or_update = async_to_raw_response_wrapper( - senders.create_or_update, + self.patch = async_to_raw_response_wrapper( + senders.patch, ) @@ -329,14 +329,14 @@ class SendersResourceWithStreamingResponse: def __init__(self, senders: SendersResource) -> None: self._senders = senders + self.create = to_streamed_response_wrapper( + senders.create, + ) self.retrieve = to_streamed_response_wrapper( senders.retrieve, ) - self.update = to_streamed_response_wrapper( - senders.update, - ) - self.create_or_update = to_streamed_response_wrapper( - senders.create_or_update, + self.patch = to_streamed_response_wrapper( + senders.patch, ) @@ -344,12 +344,12 @@ class AsyncSendersResourceWithStreamingResponse: def __init__(self, senders: AsyncSendersResource) -> None: self._senders = senders + self.create = async_to_streamed_response_wrapper( + senders.create, + ) self.retrieve = async_to_streamed_response_wrapper( senders.retrieve, ) - self.update = async_to_streamed_response_wrapper( - senders.update, - ) - self.create_or_update = async_to_streamed_response_wrapper( - senders.create_or_update, + self.patch = async_to_streamed_response_wrapper( + senders.patch, ) diff --git a/src/everos/resources/v1/settings.py b/src/everos/resources/v1/settings.py index 5e8b839..46ef20e 100644 --- a/src/everos/resources/v1/settings.py +++ b/src/everos/resources/v1/settings.py @@ -20,6 +20,7 @@ ) from ..._base_client import make_request_options from ...types.v1.settings_api_response import SettingsAPIResponse +from ...types.v1.llm_custom_setting_param import LlmCustomSettingParam __all__ = ["SettingsResource", "AsyncSettingsResource"] @@ -33,7 +34,7 @@ def with_raw_response(self) -> SettingsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return SettingsResourceWithRawResponse(self) @@ -42,7 +43,7 @@ def with_streaming_response(self) -> SettingsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return SettingsResourceWithStreamingResponse(self) @@ -70,7 +71,7 @@ def update( *, boundary_detection_timeout: Optional[int] | Omit = omit, extraction_mode: Optional[Literal["default", "pro"]] | Omit = omit, - llm_custom_setting: Optional[setting_update_params.LlmCustomSetting] | Omit = omit, + llm_custom_setting: Optional[LlmCustomSettingParam] | Omit = omit, offline_profile_extraction_interval: Optional[int] | Omit = omit, timezone: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. @@ -132,7 +133,7 @@ def with_raw_response(self) -> AsyncSettingsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncSettingsResourceWithRawResponse(self) @@ -141,7 +142,7 @@ def with_streaming_response(self) -> AsyncSettingsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncSettingsResourceWithStreamingResponse(self) @@ -169,7 +170,7 @@ async def update( *, boundary_detection_timeout: Optional[int] | Omit = omit, extraction_mode: Optional[Literal["default", "pro"]] | Omit = omit, - llm_custom_setting: Optional[setting_update_params.LlmCustomSetting] | Omit = omit, + llm_custom_setting: Optional[LlmCustomSettingParam] | Omit = omit, offline_profile_extraction_interval: Optional[int] | Omit = omit, timezone: Optional[str] | Omit = omit, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. diff --git a/src/everos/resources/v1/tasks.py b/src/everos/resources/v1/tasks.py new file mode 100644 index 0000000..9d8af3d --- /dev/null +++ b/src/everos/resources/v1/tasks.py @@ -0,0 +1,174 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +import httpx + +from ..._types import Body, Query, Headers, NotGiven, not_given +from ..._utils import path_template +from ..._compat import cached_property +from ..._resource import SyncAPIResource, AsyncAPIResource +from ..._response import ( + to_raw_response_wrapper, + to_streamed_response_wrapper, + async_to_raw_response_wrapper, + async_to_streamed_response_wrapper, +) +from ..._base_client import make_request_options +from ...types.v1.get_task_status_response import GetTaskStatusResponse + +__all__ = ["TasksResource", "AsyncTasksResource"] + + +class TasksResource(SyncAPIResource): + """Async task status tracking""" + + @cached_property + def with_raw_response(self) -> TasksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + """ + return TasksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> TasksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/everos/everos-python#with_streaming_response + """ + return TasksResourceWithStreamingResponse(self) + + def retrieve( + self, + task_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GetTaskStatusResponse: + """Query the processing status of a specific async task by task_id. + + Task status is + stored in Redis with a TTL of 1 hour. Internal status 'start' is mapped to + 'processing' in the response. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not task_id: + raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + return self._get( + path_template("/api/v1/tasks/{task_id}", task_id=task_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GetTaskStatusResponse, + ) + + +class AsyncTasksResource(AsyncAPIResource): + """Async task status tracking""" + + @cached_property + def with_raw_response(self) -> AsyncTasksResourceWithRawResponse: + """ + This property can be used as a prefix for any HTTP method call to return + the raw response object instead of the parsed content. + + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + """ + return AsyncTasksResourceWithRawResponse(self) + + @cached_property + def with_streaming_response(self) -> AsyncTasksResourceWithStreamingResponse: + """ + An alternative to `.with_raw_response` that doesn't eagerly read the response body. + + For more information, see https://www.github.com/everos/everos-python#with_streaming_response + """ + return AsyncTasksResourceWithStreamingResponse(self) + + async def retrieve( + self, + task_id: str, + *, + # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. + # The extra values given here take precedence over values defined on the client or passed to this method. + extra_headers: Headers | None = None, + extra_query: Query | None = None, + extra_body: Body | None = None, + timeout: float | httpx.Timeout | None | NotGiven = not_given, + ) -> GetTaskStatusResponse: + """Query the processing status of a specific async task by task_id. + + Task status is + stored in Redis with a TTL of 1 hour. Internal status 'start' is mapped to + 'processing' in the response. + + Args: + extra_headers: Send extra headers + + extra_query: Add additional query parameters to the request + + extra_body: Add additional JSON properties to the request + + timeout: Override the client-level default timeout for this request, in seconds + """ + if not task_id: + raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") + return await self._get( + path_template("/api/v1/tasks/{task_id}", task_id=task_id), + options=make_request_options( + extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout + ), + cast_to=GetTaskStatusResponse, + ) + + +class TasksResourceWithRawResponse: + def __init__(self, tasks: TasksResource) -> None: + self._tasks = tasks + + self.retrieve = to_raw_response_wrapper( + tasks.retrieve, + ) + + +class AsyncTasksResourceWithRawResponse: + def __init__(self, tasks: AsyncTasksResource) -> None: + self._tasks = tasks + + self.retrieve = async_to_raw_response_wrapper( + tasks.retrieve, + ) + + +class TasksResourceWithStreamingResponse: + def __init__(self, tasks: TasksResource) -> None: + self._tasks = tasks + + self.retrieve = to_streamed_response_wrapper( + tasks.retrieve, + ) + + +class AsyncTasksResourceWithStreamingResponse: + def __init__(self, tasks: AsyncTasksResource) -> None: + self._tasks = tasks + + self.retrieve = async_to_streamed_response_wrapper( + tasks.retrieve, + ) diff --git a/src/everos/resources/v1/v1.py b/src/everos/resources/v1/v1.py index 1924c8a..84ee3e4 100644 --- a/src/everos/resources/v1/v1.py +++ b/src/everos/resources/v1/v1.py @@ -2,8 +2,14 @@ from __future__ import annotations -import httpx - +from .tasks import ( + TasksResource, + AsyncTasksResource, + TasksResourceWithRawResponse, + AsyncTasksResourceWithRawResponse, + TasksResourceWithStreamingResponse, + AsyncTasksResourceWithStreamingResponse, +) from .groups import ( GroupsResource, AsyncGroupsResource, @@ -28,8 +34,6 @@ SendersResourceWithStreamingResponse, AsyncSendersResourceWithStreamingResponse, ) -from ..._types import Body, Query, Headers, NotGiven, not_given -from ..._utils import path_template from .settings import ( SettingsResource, AsyncSettingsResource, @@ -40,13 +44,6 @@ ) from ..._compat import cached_property from ..._resource import SyncAPIResource, AsyncAPIResource -from ..._response import ( - to_raw_response_wrapper, - to_streamed_response_wrapper, - async_to_raw_response_wrapper, - async_to_streamed_response_wrapper, -) -from ..._base_client import make_request_options from .memories.memories import ( MemoriesResource, AsyncMemoriesResource, @@ -55,38 +52,40 @@ MemoriesResourceWithStreamingResponse, AsyncMemoriesResourceWithStreamingResponse, ) -from ...types.v1_query_task_status_response import V1QueryTaskStatusResponse __all__ = ["V1Resource", "AsyncV1Resource"] class V1Resource(SyncAPIResource): - """Async task status tracking""" - - @cached_property - def memories(self) -> MemoriesResource: - """Memory ingestion, retrieval, search, and deletion""" - return MemoriesResource(self._client) - @cached_property def groups(self) -> GroupsResource: """Group resource CRUD operations""" return GroupsResource(self._client) + @cached_property + def settings(self) -> SettingsResource: + """Global settings management""" + return SettingsResource(self._client) + @cached_property def senders(self) -> SendersResource: """Sender resource CRUD operations""" return SendersResource(self._client) + @cached_property + def memories(self) -> MemoriesResource: + """Memory ingestion, retrieval, search, and deletion""" + return MemoriesResource(self._client) + @cached_property def object(self) -> ObjectResource: """Multimodal content storage (pre-signed upload)""" return ObjectResource(self._client) @cached_property - def settings(self) -> SettingsResource: - """Global settings management""" - return SettingsResource(self._client) + def tasks(self) -> TasksResource: + """Async task status tracking""" + return TasksResource(self._client) @cached_property def with_raw_response(self) -> V1ResourceWithRawResponse: @@ -94,7 +93,7 @@ def with_raw_response(self) -> V1ResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return V1ResourceWithRawResponse(self) @@ -103,74 +102,41 @@ def with_streaming_response(self) -> V1ResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return V1ResourceWithStreamingResponse(self) - def query_task_status( - self, - task_id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> V1QueryTaskStatusResponse: - """Query the processing status of a specific async task by task_id. - - Task status is - stored in Redis with a TTL of 1 hour. Internal status 'start' is mapped to - 'processing' in the response. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not task_id: - raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") - return self._get( - path_template("/api/v1/tasks/{task_id}", task_id=task_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=V1QueryTaskStatusResponse, - ) - class AsyncV1Resource(AsyncAPIResource): - """Async task status tracking""" - - @cached_property - def memories(self) -> AsyncMemoriesResource: - """Memory ingestion, retrieval, search, and deletion""" - return AsyncMemoriesResource(self._client) - @cached_property def groups(self) -> AsyncGroupsResource: """Group resource CRUD operations""" return AsyncGroupsResource(self._client) + @cached_property + def settings(self) -> AsyncSettingsResource: + """Global settings management""" + return AsyncSettingsResource(self._client) + @cached_property def senders(self) -> AsyncSendersResource: """Sender resource CRUD operations""" return AsyncSendersResource(self._client) + @cached_property + def memories(self) -> AsyncMemoriesResource: + """Memory ingestion, retrieval, search, and deletion""" + return AsyncMemoriesResource(self._client) + @cached_property def object(self) -> AsyncObjectResource: """Multimodal content storage (pre-signed upload)""" return AsyncObjectResource(self._client) @cached_property - def settings(self) -> AsyncSettingsResource: - """Global settings management""" - return AsyncSettingsResource(self._client) + def tasks(self) -> AsyncTasksResource: + """Async task status tracking""" + return AsyncTasksResource(self._client) @cached_property def with_raw_response(self) -> AsyncV1ResourceWithRawResponse: @@ -178,7 +144,7 @@ def with_raw_response(self) -> AsyncV1ResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncV1ResourceWithRawResponse(self) @@ -187,178 +153,146 @@ def with_streaming_response(self) -> AsyncV1ResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response + For more information, see https://www.github.com/everos/everos-python#with_streaming_response """ return AsyncV1ResourceWithStreamingResponse(self) - async def query_task_status( - self, - task_id: str, - *, - # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs. - # The extra values given here take precedence over values defined on the client or passed to this method. - extra_headers: Headers | None = None, - extra_query: Query | None = None, - extra_body: Body | None = None, - timeout: float | httpx.Timeout | None | NotGiven = not_given, - ) -> V1QueryTaskStatusResponse: - """Query the processing status of a specific async task by task_id. - - Task status is - stored in Redis with a TTL of 1 hour. Internal status 'start' is mapped to - 'processing' in the response. - - Args: - extra_headers: Send extra headers - - extra_query: Add additional query parameters to the request - - extra_body: Add additional JSON properties to the request - - timeout: Override the client-level default timeout for this request, in seconds - """ - if not task_id: - raise ValueError(f"Expected a non-empty value for `task_id` but received {task_id!r}") - return await self._get( - path_template("/api/v1/tasks/{task_id}", task_id=task_id), - options=make_request_options( - extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout - ), - cast_to=V1QueryTaskStatusResponse, - ) - class V1ResourceWithRawResponse: def __init__(self, v1: V1Resource) -> None: self._v1 = v1 - self.query_task_status = to_raw_response_wrapper( - v1.query_task_status, - ) - - @cached_property - def memories(self) -> MemoriesResourceWithRawResponse: - """Memory ingestion, retrieval, search, and deletion""" - return MemoriesResourceWithRawResponse(self._v1.memories) - @cached_property def groups(self) -> GroupsResourceWithRawResponse: """Group resource CRUD operations""" return GroupsResourceWithRawResponse(self._v1.groups) + @cached_property + def settings(self) -> SettingsResourceWithRawResponse: + """Global settings management""" + return SettingsResourceWithRawResponse(self._v1.settings) + @cached_property def senders(self) -> SendersResourceWithRawResponse: """Sender resource CRUD operations""" return SendersResourceWithRawResponse(self._v1.senders) + @cached_property + def memories(self) -> MemoriesResourceWithRawResponse: + """Memory ingestion, retrieval, search, and deletion""" + return MemoriesResourceWithRawResponse(self._v1.memories) + @cached_property def object(self) -> ObjectResourceWithRawResponse: """Multimodal content storage (pre-signed upload)""" return ObjectResourceWithRawResponse(self._v1.object) @cached_property - def settings(self) -> SettingsResourceWithRawResponse: - """Global settings management""" - return SettingsResourceWithRawResponse(self._v1.settings) + def tasks(self) -> TasksResourceWithRawResponse: + """Async task status tracking""" + return TasksResourceWithRawResponse(self._v1.tasks) class AsyncV1ResourceWithRawResponse: def __init__(self, v1: AsyncV1Resource) -> None: self._v1 = v1 - self.query_task_status = async_to_raw_response_wrapper( - v1.query_task_status, - ) - - @cached_property - def memories(self) -> AsyncMemoriesResourceWithRawResponse: - """Memory ingestion, retrieval, search, and deletion""" - return AsyncMemoriesResourceWithRawResponse(self._v1.memories) - @cached_property def groups(self) -> AsyncGroupsResourceWithRawResponse: """Group resource CRUD operations""" return AsyncGroupsResourceWithRawResponse(self._v1.groups) + @cached_property + def settings(self) -> AsyncSettingsResourceWithRawResponse: + """Global settings management""" + return AsyncSettingsResourceWithRawResponse(self._v1.settings) + @cached_property def senders(self) -> AsyncSendersResourceWithRawResponse: """Sender resource CRUD operations""" return AsyncSendersResourceWithRawResponse(self._v1.senders) + @cached_property + def memories(self) -> AsyncMemoriesResourceWithRawResponse: + """Memory ingestion, retrieval, search, and deletion""" + return AsyncMemoriesResourceWithRawResponse(self._v1.memories) + @cached_property def object(self) -> AsyncObjectResourceWithRawResponse: """Multimodal content storage (pre-signed upload)""" return AsyncObjectResourceWithRawResponse(self._v1.object) @cached_property - def settings(self) -> AsyncSettingsResourceWithRawResponse: - """Global settings management""" - return AsyncSettingsResourceWithRawResponse(self._v1.settings) + def tasks(self) -> AsyncTasksResourceWithRawResponse: + """Async task status tracking""" + return AsyncTasksResourceWithRawResponse(self._v1.tasks) class V1ResourceWithStreamingResponse: def __init__(self, v1: V1Resource) -> None: self._v1 = v1 - self.query_task_status = to_streamed_response_wrapper( - v1.query_task_status, - ) - - @cached_property - def memories(self) -> MemoriesResourceWithStreamingResponse: - """Memory ingestion, retrieval, search, and deletion""" - return MemoriesResourceWithStreamingResponse(self._v1.memories) - @cached_property def groups(self) -> GroupsResourceWithStreamingResponse: """Group resource CRUD operations""" return GroupsResourceWithStreamingResponse(self._v1.groups) + @cached_property + def settings(self) -> SettingsResourceWithStreamingResponse: + """Global settings management""" + return SettingsResourceWithStreamingResponse(self._v1.settings) + @cached_property def senders(self) -> SendersResourceWithStreamingResponse: """Sender resource CRUD operations""" return SendersResourceWithStreamingResponse(self._v1.senders) + @cached_property + def memories(self) -> MemoriesResourceWithStreamingResponse: + """Memory ingestion, retrieval, search, and deletion""" + return MemoriesResourceWithStreamingResponse(self._v1.memories) + @cached_property def object(self) -> ObjectResourceWithStreamingResponse: """Multimodal content storage (pre-signed upload)""" return ObjectResourceWithStreamingResponse(self._v1.object) @cached_property - def settings(self) -> SettingsResourceWithStreamingResponse: - """Global settings management""" - return SettingsResourceWithStreamingResponse(self._v1.settings) + def tasks(self) -> TasksResourceWithStreamingResponse: + """Async task status tracking""" + return TasksResourceWithStreamingResponse(self._v1.tasks) class AsyncV1ResourceWithStreamingResponse: def __init__(self, v1: AsyncV1Resource) -> None: self._v1 = v1 - self.query_task_status = async_to_streamed_response_wrapper( - v1.query_task_status, - ) - - @cached_property - def memories(self) -> AsyncMemoriesResourceWithStreamingResponse: - """Memory ingestion, retrieval, search, and deletion""" - return AsyncMemoriesResourceWithStreamingResponse(self._v1.memories) - @cached_property def groups(self) -> AsyncGroupsResourceWithStreamingResponse: """Group resource CRUD operations""" return AsyncGroupsResourceWithStreamingResponse(self._v1.groups) + @cached_property + def settings(self) -> AsyncSettingsResourceWithStreamingResponse: + """Global settings management""" + return AsyncSettingsResourceWithStreamingResponse(self._v1.settings) + @cached_property def senders(self) -> AsyncSendersResourceWithStreamingResponse: """Sender resource CRUD operations""" return AsyncSendersResourceWithStreamingResponse(self._v1.senders) + @cached_property + def memories(self) -> AsyncMemoriesResourceWithStreamingResponse: + """Memory ingestion, retrieval, search, and deletion""" + return AsyncMemoriesResourceWithStreamingResponse(self._v1.memories) + @cached_property def object(self) -> AsyncObjectResourceWithStreamingResponse: """Multimodal content storage (pre-signed upload)""" return AsyncObjectResourceWithStreamingResponse(self._v1.object) @cached_property - def settings(self) -> AsyncSettingsResourceWithStreamingResponse: - """Global settings management""" - return AsyncSettingsResourceWithStreamingResponse(self._v1.settings) + def tasks(self) -> AsyncTasksResourceWithStreamingResponse: + """Async task status tracking""" + return AsyncTasksResourceWithStreamingResponse(self._v1.tasks) diff --git a/src/everos/types/__init__.py b/src/everos/types/__init__.py index 1ac372f..f8ee8b1 100644 --- a/src/everos/types/__init__.py +++ b/src/everos/types/__init__.py @@ -1,5 +1,3 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from __future__ import annotations - -from .v1_query_task_status_response import V1QueryTaskStatusResponse as V1QueryTaskStatusResponse diff --git a/src/everos/types/v1/__init__.py b/src/everos/types/v1/__init__.py index 1a812ee..5d901dd 100644 --- a/src/everos/types/v1/__init__.py +++ b/src/everos/types/v1/__init__.py @@ -2,24 +2,41 @@ from __future__ import annotations +from .add_result import AddResult as AddResult from .add_response import AddResponse as AddResponse +from .episode_item import EpisodeItem as EpisodeItem +from .flush_result import FlushResult as FlushResult +from .profile_item import ProfileItem as ProfileItem from .flush_response import FlushResponse as FlushResponse +from .group_response import GroupResponse as GroupResponse +from .raw_message_dto import RawMessageDto as RawMessageDto +from .sender_response import SenderResponse as SenderResponse +from .get_mem_response import GetMemResponse as GetMemResponse +from .object_sign_item import ObjectSignItem as ObjectSignItem +from .memory_add_params import MemoryAddParams as MemoryAddParams from .memory_get_params import MemoryGetParams as MemoryGetParams +from .settings_response import SettingsResponse as SettingsResponse from .content_item_param import ContentItemParam as ContentItemParam from .group_api_response import GroupAPIResponse as GroupAPIResponse -from .group_update_params import GroupUpdateParams as GroupUpdateParams +from .group_patch_params import GroupPatchParams as GroupPatchParams +from .message_item_param import MessageItemParam as MessageItemParam +from .object_sign_params import ObjectSignParams as ObjectSignParams +from .object_signed_info import ObjectSignedInfo as ObjectSignedInfo +from .task_status_result import TaskStatusResult as TaskStatusResult +from .group_create_params import GroupCreateParams as GroupCreateParams from .memory_flush_params import MemoryFlushParams as MemoryFlushParams -from .memory_get_response import MemoryGetResponse as MemoryGetResponse from .sender_api_response import SenderAPIResponse as SenderAPIResponse -from .memory_create_params import MemoryCreateParams as MemoryCreateParams +from .sender_patch_params import SenderPatchParams as SenderPatchParams from .memory_delete_params import MemoryDeleteParams as MemoryDeleteParams from .memory_search_params import MemorySearchParams as MemorySearchParams -from .sender_update_params import SenderUpdateParams as SenderUpdateParams +from .object_sign_response import ObjectSignResponse as ObjectSignResponse +from .sender_create_params import SenderCreateParams as SenderCreateParams +from .get_memories_response import GetMemoriesResponse as GetMemoriesResponse from .setting_update_params import SettingUpdateParams as SettingUpdateParams from .settings_api_response import SettingsAPIResponse as SettingsAPIResponse -from .memory_search_response import MemorySearchResponse as MemorySearchResponse +from .get_task_status_response import GetTaskStatusResponse as GetTaskStatusResponse +from .llm_custom_setting_param import LlmCustomSettingParam as LlmCustomSettingParam +from .search_memories_response import SearchMemoriesResponse as SearchMemoriesResponse from .llm_provider_config_param import LlmProviderConfigParam as LlmProviderConfigParam -from .group_create_or_update_params import GroupCreateOrUpdateParams as GroupCreateOrUpdateParams -from .sender_create_or_update_params import SenderCreateOrUpdateParams as SenderCreateOrUpdateParams -from .object_get_presigned_url_params import ObjectGetPresignedURLParams as ObjectGetPresignedURLParams -from .object_get_presigned_url_response import ObjectGetPresignedURLResponse as ObjectGetPresignedURLResponse +from .search_memories_response_data import SearchMemoriesResponseData as SearchMemoriesResponseData +from .object_sign_item_request_param import ObjectSignItemRequestParam as ObjectSignItemRequestParam diff --git a/src/everos/types/v1/add_response.py b/src/everos/types/v1/add_response.py index 2875c75..79d38f4 100644 --- a/src/everos/types/v1/add_response.py +++ b/src/everos/types/v1/add_response.py @@ -1,26 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional -from typing_extensions import Literal from ..._models import BaseModel +from .add_result import AddResult -__all__ = ["AddResponse", "Data"] - - -class Data(BaseModel): - message: Optional[str] = None - """Human-readable status description""" - - message_count: Optional[int] = None - """Number of messages accepted""" - - status: Optional[Literal["accumulated", "extracted"]] = None - """Processing status""" - - task_id: Optional[str] = None - """Task tracking ID""" +__all__ = ["AddResponse"] class AddResponse(BaseModel): - data: Optional[Data] = None + data: Optional[AddResult] = None diff --git a/src/everos/types/v1/add_result.py b/src/everos/types/v1/add_result.py new file mode 100644 index 0000000..c057b84 --- /dev/null +++ b/src/everos/types/v1/add_result.py @@ -0,0 +1,22 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["AddResult"] + + +class AddResult(BaseModel): + message: Optional[str] = None + """Human-readable status description""" + + message_count: Optional[int] = None + """Number of messages accepted""" + + status: Optional[Literal["accumulated", "extracted"]] = None + """Processing status""" + + task_id: Optional[str] = None + """Task tracking ID""" diff --git a/src/everos/types/v1/episode_item.py b/src/everos/types/v1/episode_item.py new file mode 100644 index 0000000..bcf189d --- /dev/null +++ b/src/everos/types/v1/episode_item.py @@ -0,0 +1,51 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional +from datetime import datetime + +from ..._models import BaseModel + +__all__ = ["EpisodeItem"] + + +class EpisodeItem(BaseModel): + """Episodic memory item""" + + id: str + """MongoDB ObjectId as string""" + + episode: Optional[str] = None + """Full episodic memory text""" + + group_id: Optional[str] = None + """Group ID""" + + parent_id: Optional[str] = None + """Parent memory ID""" + + parent_type: Optional[str] = None + """Parent memory type""" + + participants: Optional[List[str]] = None + """Event participant names""" + + sender_ids: Optional[List[str]] = None + """Sender IDs of event participants""" + + session_id: Optional[str] = None + """Session identifier""" + + subject: Optional[str] = None + """Memory subject""" + + summary: Optional[str] = None + """Memory summary""" + + timestamp: Optional[datetime] = None + """Event occurrence time""" + + type: Optional[str] = None + """Episode type""" + + user_id: Optional[str] = None + """Owner user ID""" diff --git a/src/everos/types/v1/flush_response.py b/src/everos/types/v1/flush_response.py index ee76bac..42f4cbf 100644 --- a/src/everos/types/v1/flush_response.py +++ b/src/everos/types/v1/flush_response.py @@ -1,23 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. from typing import Optional -from typing_extensions import Literal from ..._models import BaseModel +from .flush_result import FlushResult -__all__ = ["FlushResponse", "Data"] - - -class Data(BaseModel): - message: Optional[str] = None - """Human-readable status description""" - - request_id: Optional[str] = None - """Request tracking ID (reserved)""" - - status: Optional[Literal["extracted", "no_extraction"]] = None - """Processing status""" +__all__ = ["FlushResponse"] class FlushResponse(BaseModel): - data: Optional[Data] = None + data: Optional[FlushResult] = None diff --git a/src/everos/types/v1/flush_result.py b/src/everos/types/v1/flush_result.py new file mode 100644 index 0000000..1ecde79 --- /dev/null +++ b/src/everos/types/v1/flush_result.py @@ -0,0 +1,19 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from typing_extensions import Literal + +from ..._models import BaseModel + +__all__ = ["FlushResult"] + + +class FlushResult(BaseModel): + message: Optional[str] = None + """Human-readable status description""" + + request_id: Optional[str] = None + """Request tracking ID (reserved)""" + + status: Optional[Literal["extracted", "no_extraction"]] = None + """Processing status""" diff --git a/src/everos/types/v1/get_mem_response.py b/src/everos/types/v1/get_mem_response.py new file mode 100644 index 0000000..1220e78 --- /dev/null +++ b/src/everos/types/v1/get_mem_response.py @@ -0,0 +1,33 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ..._models import BaseModel +from .episode_item import EpisodeItem +from .profile_item import ProfileItem +from .memories.agent_case_item import AgentCaseItem +from .memories.agent_skill_item import AgentSkillItem + +__all__ = ["GetMemResponse"] + + +class GetMemResponse(BaseModel): + """Memory get result data""" + + agent_cases: Optional[List[AgentCaseItem]] = None + """Agent case items""" + + agent_skills: Optional[List[AgentSkillItem]] = None + """Agent skill items""" + + count: Optional[int] = None + """Records in current page""" + + episodes: Optional[List[EpisodeItem]] = None + """Episodic memory items""" + + profiles: Optional[List[ProfileItem]] = None + """Profile items""" + + total_count: Optional[int] = None + """Total records matching filters""" diff --git a/src/everos/types/v1/get_memories_response.py b/src/everos/types/v1/get_memories_response.py new file mode 100644 index 0000000..d2876ee --- /dev/null +++ b/src/everos/types/v1/get_memories_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .get_mem_response import GetMemResponse + +__all__ = ["GetMemoriesResponse"] + + +class GetMemoriesResponse(BaseModel): + data: Optional[GetMemResponse] = None + """Memory get result data""" diff --git a/src/everos/types/v1/get_task_status_response.py b/src/everos/types/v1/get_task_status_response.py new file mode 100644 index 0000000..77e01fb --- /dev/null +++ b/src/everos/types/v1/get_task_status_response.py @@ -0,0 +1,10 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from ..._models import BaseModel +from .task_status_result import TaskStatusResult + +__all__ = ["GetTaskStatusResponse"] + + +class GetTaskStatusResponse(BaseModel): + data: TaskStatusResult diff --git a/src/everos/types/v1/group_api_response.py b/src/everos/types/v1/group_api_response.py index 9493d55..08f1fe1 100644 --- a/src/everos/types/v1/group_api_response.py +++ b/src/everos/types/v1/group_api_response.py @@ -3,26 +3,10 @@ from typing import Optional from ..._models import BaseModel +from .group_response import GroupResponse -__all__ = ["GroupAPIResponse", "Data"] - - -class Data(BaseModel): - created_at: str - """Creation time (ISO 8601)""" - - group_id: str - """Group identifier""" - - updated_at: str - """Last update time (ISO 8601)""" - - description: Optional[str] = None - """Group description""" - - name: Optional[str] = None - """Group display name""" +__all__ = ["GroupAPIResponse"] class GroupAPIResponse(BaseModel): - data: Optional[Data] = None + data: Optional[GroupResponse] = None diff --git a/src/everos/types/v1/group_create_or_update_params.py b/src/everos/types/v1/group_create_params.py similarity index 79% rename from src/everos/types/v1/group_create_or_update_params.py rename to src/everos/types/v1/group_create_params.py index 2b98113..0ea652c 100644 --- a/src/everos/types/v1/group_create_or_update_params.py +++ b/src/everos/types/v1/group_create_params.py @@ -5,10 +5,10 @@ from typing import Optional from typing_extensions import Required, TypedDict -__all__ = ["GroupCreateOrUpdateParams"] +__all__ = ["GroupCreateParams"] -class GroupCreateOrUpdateParams(TypedDict, total=False): +class GroupCreateParams(TypedDict, total=False): group_id: Required[str] """Group identifier (unique)""" diff --git a/src/everos/types/v1/group_update_params.py b/src/everos/types/v1/group_patch_params.py similarity index 79% rename from src/everos/types/v1/group_update_params.py rename to src/everos/types/v1/group_patch_params.py index 0627dbd..f319b7e 100644 --- a/src/everos/types/v1/group_update_params.py +++ b/src/everos/types/v1/group_patch_params.py @@ -5,10 +5,10 @@ from typing import Optional from typing_extensions import TypedDict -__all__ = ["GroupUpdateParams"] +__all__ = ["GroupPatchParams"] -class GroupUpdateParams(TypedDict, total=False): +class GroupPatchParams(TypedDict, total=False): description: Optional[str] """New group description""" diff --git a/src/everos/types/v1/group_response.py b/src/everos/types/v1/group_response.py new file mode 100644 index 0000000..65c901a --- /dev/null +++ b/src/everos/types/v1/group_response.py @@ -0,0 +1,24 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["GroupResponse"] + + +class GroupResponse(BaseModel): + created_at: str + """Creation time (ISO 8601)""" + + group_id: str + """Group identifier""" + + updated_at: str + """Last update time (ISO 8601)""" + + description: Optional[str] = None + """Group description""" + + name: Optional[str] = None + """Group display name""" diff --git a/src/everos/types/v1/llm_custom_setting_param.py b/src/everos/types/v1/llm_custom_setting_param.py new file mode 100644 index 0000000..10d412a --- /dev/null +++ b/src/everos/types/v1/llm_custom_setting_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Optional +from typing_extensions import TypedDict + +from .llm_provider_config_param import LlmProviderConfigParam + +__all__ = ["LlmCustomSettingParam"] + + +class LlmCustomSettingParam(TypedDict, total=False): + boundary: Optional[LlmProviderConfigParam] + """LLM config for boundary detection (fast, cheap model recommended)""" + + extra: Optional[Dict[str, object]] + """Additional task-specific LLM configurations""" + + extraction: Optional[LlmProviderConfigParam] + """LLM config for memory extraction (high quality model recommended)""" diff --git a/src/everos/types/v1/memories/__init__.py b/src/everos/types/v1/memories/__init__.py index fa27560..3f1c387 100644 --- a/src/everos/types/v1/memories/__init__.py +++ b/src/everos/types/v1/memories/__init__.py @@ -2,7 +2,13 @@ from __future__ import annotations +from .agent_case_item import AgentCaseItem as AgentCaseItem +from .tool_call_param import ToolCallParam as ToolCallParam +from .agent_add_params import AgentAddParams as AgentAddParams +from .agent_skill_item import AgentSkillItem as AgentSkillItem +from .group_add_params import GroupAddParams as GroupAddParams from .agent_flush_params import AgentFlushParams as AgentFlushParams from .group_flush_params import GroupFlushParams as GroupFlushParams -from .agent_create_params import AgentCreateParams as AgentCreateParams -from .group_create_params import GroupCreateParams as GroupCreateParams +from .agent_message_item_param import AgentMessageItemParam as AgentMessageItemParam +from .group_message_item_param import GroupMessageItemParam as GroupMessageItemParam +from .tool_call_function_param import ToolCallFunctionParam as ToolCallFunctionParam diff --git a/src/everos/types/v1/memories/agent_add_params.py b/src/everos/types/v1/memories/agent_add_params.py new file mode 100644 index 0000000..040d0f2 --- /dev/null +++ b/src/everos/types/v1/memories/agent_add_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Required, TypedDict + +from .agent_message_item_param import AgentMessageItemParam + +__all__ = ["AgentAddParams"] + + +class AgentAddParams(TypedDict, total=False): + messages: Required[Iterable[AgentMessageItemParam]] + """Agent trajectory messages (1-500 items)""" + + user_id: Required[str] + """Owner user ID""" + + async_mode: bool + """Enable async processing. + + When true, returns 202 with task_id; when false, processes synchronously and + returns 200. + """ + + session_id: Optional[str] + """Session identifier""" diff --git a/src/everos/types/v1/memories/agent_case_item.py b/src/everos/types/v1/memories/agent_case_item.py new file mode 100644 index 0000000..29869fd --- /dev/null +++ b/src/everos/types/v1/memories/agent_case_item.py @@ -0,0 +1,42 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional +from datetime import datetime + +from ...._models import BaseModel + +__all__ = ["AgentCaseItem"] + + +class AgentCaseItem(BaseModel): + """Agent case item (task experience)""" + + id: str + """MongoDB ObjectId as string""" + + approach: Optional[str] = None + """Step-by-step approach with decisions and lessons""" + + group_id: Optional[str] = None + """Group ID""" + + parent_id: Optional[str] = None + """Parent memory ID""" + + parent_type: Optional[str] = None + """Parent memory type""" + + quality_score: Optional[float] = None + """Task completion quality score (0.0-1.0)""" + + session_id: Optional[str] = None + """Session identifier""" + + task_intent: Optional[str] = None + """Rewritten task intent as retrieval key""" + + timestamp: Optional[datetime] = None + """Task occurrence time""" + + user_id: Optional[str] = None + """User ID""" diff --git a/src/everos/types/v1/memories/agent_create_params.py b/src/everos/types/v1/memories/agent_message_item_param.py similarity index 55% rename from src/everos/types/v1/memories/agent_create_params.py rename to src/everos/types/v1/memories/agent_message_item_param.py index 099681c..9d0a17a 100644 --- a/src/everos/types/v1/memories/agent_create_params.py +++ b/src/everos/types/v1/memories/agent_message_item_param.py @@ -5,50 +5,13 @@ from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypedDict +from .tool_call_param import ToolCallParam from ..content_item_param import ContentItemParam -__all__ = ["AgentCreateParams", "Message", "MessageToolCall", "MessageToolCallFunction"] +__all__ = ["AgentMessageItemParam"] -class AgentCreateParams(TypedDict, total=False): - messages: Required[Iterable[Message]] - """Agent trajectory messages (1-500 items)""" - - user_id: Required[str] - """Owner user ID""" - - async_mode: bool - """Enable async processing. - - When true, returns 202 with task_id; when false, processes synchronously and - returns 200. - """ - - session_id: Optional[str] - """Session identifier""" - - -class MessageToolCallFunction(TypedDict, total=False): - arguments: Required[str] - """JSON-encoded arguments string""" - - name: Required[str] - """Function/tool name""" - - -class MessageToolCall(TypedDict, total=False): - """OpenAI-format tool call made by the assistant""" - - id: Required[str] - """Unique tool call ID""" - - function: Required[MessageToolCallFunction] - - type: str - """Tool call type""" - - -class Message(TypedDict, total=False): +class AgentMessageItemParam(TypedDict, total=False): """Agent trajectory message. Supports role='tool' in addition to 'user'/'assistant'. Does not support message_id or sender_name (stripped by Gateway). @@ -76,5 +39,5 @@ class Message(TypedDict, total=False): tool_call_id: Optional[str] """ID of the tool call this message responds to. Required when role='tool'.""" - tool_calls: Optional[Iterable[MessageToolCall]] + tool_calls: Optional[Iterable[ToolCallParam]] """Tool calls made by assistant (OpenAI format). Only when role='assistant'.""" diff --git a/src/everos/types/v1/memories/agent_skill_item.py b/src/everos/types/v1/memories/agent_skill_item.py new file mode 100644 index 0000000..4d3343f --- /dev/null +++ b/src/everos/types/v1/memories/agent_skill_item.py @@ -0,0 +1,41 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from ...._models import BaseModel + +__all__ = ["AgentSkillItem"] + + +class AgentSkillItem(BaseModel): + """Agent skill item (reusable skill from clustered cases)""" + + id: str + """MongoDB ObjectId as string""" + + cluster_id: Optional[str] = None + """MemScene cluster ID""" + + confidence: Optional[float] = None + """Confidence score (0.0-1.0)""" + + content: Optional[str] = None + """Full skill content""" + + description: Optional[str] = None + """What this skill does and when to use it""" + + group_id: Optional[str] = None + """Group ID""" + + maturity_score: Optional[float] = None + """Maturity score (0.0-1.0)""" + + name: Optional[str] = None + """Skill name""" + + source_case_ids: Optional[List[str]] = None + """Source AgentCase IDs""" + + user_id: Optional[str] = None + """User ID""" diff --git a/src/everos/types/v1/memories/group_add_params.py b/src/everos/types/v1/memories/group_add_params.py new file mode 100644 index 0000000..a518ff4 --- /dev/null +++ b/src/everos/types/v1/memories/group_add_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Dict, Iterable, Optional +from typing_extensions import Required, TypedDict + +from .group_message_item_param import GroupMessageItemParam + +__all__ = ["GroupAddParams"] + + +class GroupAddParams(TypedDict, total=False): + group_id: Required[str] + """Group identifier""" + + messages: Required[Iterable[GroupMessageItemParam]] + """Batch message array (1-500 items). sender_id is required per message.""" + + async_mode: bool + """Enable async processing. + + When true, returns 202 with task_id; when false, processes synchronously and + returns 200. + """ + + group_meta: Optional[Dict[str, object]] + """Group metadata""" diff --git a/src/everos/types/v1/memories/group_create_params.py b/src/everos/types/v1/memories/group_message_item_param.py similarity index 58% rename from src/everos/types/v1/memories/group_create_params.py rename to src/everos/types/v1/memories/group_message_item_param.py index 7115ae4..f7f3225 100644 --- a/src/everos/types/v1/memories/group_create_params.py +++ b/src/everos/types/v1/memories/group_message_item_param.py @@ -2,33 +2,15 @@ from __future__ import annotations -from typing import Dict, Union, Iterable, Optional +from typing import Union, Iterable, Optional from typing_extensions import Literal, Required, TypedDict from ..content_item_param import ContentItemParam -__all__ = ["GroupCreateParams", "Message"] +__all__ = ["GroupMessageItemParam"] -class GroupCreateParams(TypedDict, total=False): - group_id: Required[str] - """Group identifier""" - - messages: Required[Iterable[Message]] - """Batch message array (1-500 items). sender_id is required per message.""" - - async_mode: bool - """Enable async processing. - - When true, returns 202 with task_id; when false, processes synchronously and - returns 200. - """ - - group_meta: Optional[Dict[str, object]] - """Group metadata""" - - -class Message(TypedDict, total=False): +class GroupMessageItemParam(TypedDict, total=False): """Single message item for group memories. Each message must include sender_id to identify participants. diff --git a/src/everos/types/v1/memories/tool_call_function_param.py b/src/everos/types/v1/memories/tool_call_function_param.py new file mode 100644 index 0000000..7029e8a --- /dev/null +++ b/src/everos/types/v1/memories/tool_call_function_param.py @@ -0,0 +1,15 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +__all__ = ["ToolCallFunctionParam"] + + +class ToolCallFunctionParam(TypedDict, total=False): + arguments: Required[str] + """JSON-encoded arguments string""" + + name: Required[str] + """Function/tool name""" diff --git a/src/everos/types/v1/memories/tool_call_param.py b/src/everos/types/v1/memories/tool_call_param.py new file mode 100644 index 0000000..e08e0f4 --- /dev/null +++ b/src/everos/types/v1/memories/tool_call_param.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing_extensions import Required, TypedDict + +from .tool_call_function_param import ToolCallFunctionParam + +__all__ = ["ToolCallParam"] + + +class ToolCallParam(TypedDict, total=False): + """OpenAI-format tool call made by the assistant""" + + id: Required[str] + """Unique tool call ID""" + + function: Required[ToolCallFunctionParam] + + type: str + """Tool call type""" diff --git a/src/everos/types/v1/memory_add_params.py b/src/everos/types/v1/memory_add_params.py new file mode 100644 index 0000000..0879fb3 --- /dev/null +++ b/src/everos/types/v1/memory_add_params.py @@ -0,0 +1,28 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable, Optional +from typing_extensions import Required, TypedDict + +from .message_item_param import MessageItemParam + +__all__ = ["MemoryAddParams"] + + +class MemoryAddParams(TypedDict, total=False): + messages: Required[Iterable[MessageItemParam]] + """Batch message array (1-500 items)""" + + user_id: Required[str] + """Owner user ID""" + + async_mode: bool + """Enable async processing. + + When true, returns 202 with task_id; when false, processes synchronously and + returns 200. + """ + + session_id: Optional[str] + """Session identifier""" diff --git a/src/everos/types/v1/memory_get_response.py b/src/everos/types/v1/memory_get_response.py deleted file mode 100644 index f93790f..0000000 --- a/src/everos/types/v1/memory_get_response.py +++ /dev/null @@ -1,168 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional -from datetime import datetime - -from ..._models import BaseModel - -__all__ = ["MemoryGetResponse", "Data", "DataAgentCase", "DataAgentSkill", "DataEpisode", "DataProfile"] - - -class DataAgentCase(BaseModel): - """Agent case item (task experience)""" - - id: str - """MongoDB ObjectId as string""" - - approach: Optional[str] = None - """Step-by-step approach with decisions and lessons""" - - group_id: Optional[str] = None - """Group ID""" - - parent_id: Optional[str] = None - """Parent memory ID""" - - parent_type: Optional[str] = None - """Parent memory type""" - - quality_score: Optional[float] = None - """Task completion quality score (0.0-1.0)""" - - session_id: Optional[str] = None - """Session identifier""" - - task_intent: Optional[str] = None - """Rewritten task intent as retrieval key""" - - timestamp: Optional[datetime] = None - """Task occurrence time""" - - user_id: Optional[str] = None - """User ID""" - - -class DataAgentSkill(BaseModel): - """Agent skill item (reusable skill from clustered cases)""" - - id: str - """MongoDB ObjectId as string""" - - cluster_id: Optional[str] = None - """MemScene cluster ID""" - - confidence: Optional[float] = None - """Confidence score (0.0-1.0)""" - - content: Optional[str] = None - """Full skill content""" - - description: Optional[str] = None - """What this skill does and when to use it""" - - group_id: Optional[str] = None - """Group ID""" - - maturity_score: Optional[float] = None - """Maturity score (0.0-1.0)""" - - name: Optional[str] = None - """Skill name""" - - source_case_ids: Optional[List[str]] = None - """Source AgentCase IDs""" - - user_id: Optional[str] = None - """User ID""" - - -class DataEpisode(BaseModel): - """Episodic memory item""" - - id: str - """MongoDB ObjectId as string""" - - episode: Optional[str] = None - """Full episodic memory text""" - - group_id: Optional[str] = None - """Group ID""" - - parent_id: Optional[str] = None - """Parent memory ID""" - - parent_type: Optional[str] = None - """Parent memory type""" - - participants: Optional[List[str]] = None - """Event participant names""" - - sender_ids: Optional[List[str]] = None - """Sender IDs of event participants""" - - session_id: Optional[str] = None - """Session identifier""" - - subject: Optional[str] = None - """Memory subject""" - - summary: Optional[str] = None - """Memory summary""" - - timestamp: Optional[datetime] = None - """Event occurrence time""" - - type: Optional[str] = None - """Episode type""" - - user_id: Optional[str] = None - """Owner user ID""" - - -class DataProfile(BaseModel): - """User profile item""" - - id: str - """MongoDB ObjectId as string""" - - group_id: Optional[str] = None - """Group ID""" - - memcell_count: Optional[int] = None - """Number of MemCells""" - - profile_data: Optional[Dict[str, object]] = None - """Profile data (explicit_info, implicit_traits)""" - - scenario: Optional[str] = None - """Scenario type: solo or team""" - - user_id: Optional[str] = None - """User ID""" - - -class Data(BaseModel): - """Memory get result data""" - - agent_cases: Optional[List[DataAgentCase]] = None - """Agent case items""" - - agent_skills: Optional[List[DataAgentSkill]] = None - """Agent skill items""" - - count: Optional[int] = None - """Records in current page""" - - episodes: Optional[List[DataEpisode]] = None - """Episodic memory items""" - - profiles: Optional[List[DataProfile]] = None - """Profile items""" - - total_count: Optional[int] = None - """Total records matching filters""" - - -class MemoryGetResponse(BaseModel): - data: Optional[Data] = None - """Memory get result data""" diff --git a/src/everos/types/v1/memory_create_params.py b/src/everos/types/v1/message_item_param.py similarity index 61% rename from src/everos/types/v1/memory_create_params.py rename to src/everos/types/v1/message_item_param.py index ffbfe43..a80287d 100644 --- a/src/everos/types/v1/memory_create_params.py +++ b/src/everos/types/v1/message_item_param.py @@ -7,28 +7,10 @@ from .content_item_param import ContentItemParam -__all__ = ["MemoryCreateParams", "Message"] +__all__ = ["MessageItemParam"] -class MemoryCreateParams(TypedDict, total=False): - messages: Required[Iterable[Message]] - """Batch message array (1-500 items)""" - - user_id: Required[str] - """Owner user ID""" - - async_mode: bool - """Enable async processing. - - When true, returns 202 with task_id; when false, processes synchronously and - returns 200. - """ - - session_id: Optional[str] - """Session identifier""" - - -class Message(TypedDict, total=False): +class MessageItemParam(TypedDict, total=False): """Single message item for personal memories. Content accepts a plain string shorthand: "hello" is coerced to [{"type": "text", "text": "hello"}]. diff --git a/src/everos/types/v1/object_get_presigned_url_response.py b/src/everos/types/v1/object_get_presigned_url_response.py deleted file mode 100644 index 3b413c3..0000000 --- a/src/everos/types/v1/object_get_presigned_url_response.py +++ /dev/null @@ -1,80 +0,0 @@ -# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. - -from typing import Dict, List, Optional - -from pydantic import Field as FieldInfo - -from ..._models import BaseModel - -__all__ = [ - "ObjectGetPresignedURLResponse", - "Result", - "ResultData", - "ResultDataObjectList", - "ResultDataObjectListObjectSignedInfo", -] - - -class ResultDataObjectListObjectSignedInfo(BaseModel): - fields: Optional[Dict[str, str]] = None - """ - Form fields required for S3 POST upload (key, policy, x-amz-\\** headers, - Content-Type) - """ - - max_size: Optional[int] = FieldInfo(alias="maxSize", default=None) - """Maximum file size in bytes. image: 10MB, file: 100MB, video: 500MB""" - - url: Optional[str] = None - """Pre-signed S3 upload URL""" - - -class ResultDataObjectList(BaseModel): - """Signed object item. - - MMS echoes back the request fields alongside generated signing info. - """ - - file_id: Optional[str] = FieldInfo(alias="fileId", default=None) - """Echoed file ID from request""" - - file_name: Optional[str] = FieldInfo(alias="fileName", default=None) - """Echoed file name from request""" - - file_type: Optional[str] = FieldInfo(alias="fileType", default=None) - """Echoed file type from request""" - - object_key: Optional[str] = FieldInfo(alias="objectKey", default=None) - """S3 object key (generated by MMS)""" - - object_signed_info: Optional[ResultDataObjectListObjectSignedInfo] = FieldInfo( - alias="objectSignedInfo", default=None - ) - - -class ResultData(BaseModel): - """On success: contains objectList. On validation error: string error message.""" - - object_list: Optional[List[ResultDataObjectList]] = FieldInfo(alias="objectList", default=None) - - -class Result(BaseModel): - data: Optional[ResultData] = None - """On success: contains objectList. On validation error: string error message.""" - - -class ObjectGetPresignedURLResponse(BaseModel): - """ - MMS service response format (Gateway transparent proxy, not standard ErrorResponse format) - """ - - error: Optional[str] = None - """Error message. 'OK' on success, error description on failure""" - - request_id: Optional[str] = None - """MMS request tracking ID""" - - result: Optional[Result] = None - - status: Optional[int] = None - """MMS status code. 0 = success, 2018 = validation error""" diff --git a/src/everos/types/v1/object_sign_item.py b/src/everos/types/v1/object_sign_item.py new file mode 100644 index 0000000..d2769b6 --- /dev/null +++ b/src/everos/types/v1/object_sign_item.py @@ -0,0 +1,31 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from .object_signed_info import ObjectSignedInfo + +__all__ = ["ObjectSignItem"] + + +class ObjectSignItem(BaseModel): + """Signed object item. + + MMS echoes back the request fields alongside generated signing info. + """ + + file_id: Optional[str] = FieldInfo(alias="fileId", default=None) + """Echoed file ID from request""" + + file_name: Optional[str] = FieldInfo(alias="fileName", default=None) + """Echoed file name from request""" + + file_type: Optional[str] = FieldInfo(alias="fileType", default=None) + """Echoed file type from request""" + + object_key: Optional[str] = FieldInfo(alias="objectKey", default=None) + """S3 object key (generated by MMS)""" + + object_signed_info: Optional[ObjectSignedInfo] = FieldInfo(alias="objectSignedInfo", default=None) diff --git a/src/everos/types/v1/object_get_presigned_url_params.py b/src/everos/types/v1/object_sign_item_request_param.py similarity index 67% rename from src/everos/types/v1/object_get_presigned_url_params.py rename to src/everos/types/v1/object_sign_item_request_param.py index 2a5ffa4..33350ac 100644 --- a/src/everos/types/v1/object_get_presigned_url_params.py +++ b/src/everos/types/v1/object_sign_item_request_param.py @@ -2,20 +2,14 @@ from __future__ import annotations -from typing import Iterable from typing_extensions import Literal, Required, Annotated, TypedDict from ..._utils import PropertyInfo -__all__ = ["ObjectGetPresignedURLParams", "ObjectList"] +__all__ = ["ObjectSignItemRequestParam"] -class ObjectGetPresignedURLParams(TypedDict, total=False): - object_list: Required[Annotated[Iterable[ObjectList], PropertyInfo(alias="objectList")]] - """List of files to sign (supports batch signing)""" - - -class ObjectList(TypedDict, total=False): +class ObjectSignItemRequestParam(TypedDict, total=False): """Single file sign request item""" file_id: Required[Annotated[str, PropertyInfo(alias="fileId")]] diff --git a/src/everos/types/v1/object_sign_params.py b/src/everos/types/v1/object_sign_params.py new file mode 100644 index 0000000..5857440 --- /dev/null +++ b/src/everos/types/v1/object_sign_params.py @@ -0,0 +1,16 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from __future__ import annotations + +from typing import Iterable +from typing_extensions import Required, Annotated, TypedDict + +from ..._utils import PropertyInfo +from .object_sign_item_request_param import ObjectSignItemRequestParam + +__all__ = ["ObjectSignParams"] + + +class ObjectSignParams(TypedDict, total=False): + object_list: Required[Annotated[Iterable[ObjectSignItemRequestParam], PropertyInfo(alias="objectList")]] + """List of files to sign (supports batch signing)""" diff --git a/src/everos/types/v1/object_sign_response.py b/src/everos/types/v1/object_sign_response.py new file mode 100644 index 0000000..6dda9b7 --- /dev/null +++ b/src/everos/types/v1/object_sign_response.py @@ -0,0 +1,38 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import List, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel +from .object_sign_item import ObjectSignItem + +__all__ = ["ObjectSignResponse", "Result", "ResultData"] + + +class ResultData(BaseModel): + """On success: contains objectList. On validation error: string error message.""" + + object_list: Optional[List[ObjectSignItem]] = FieldInfo(alias="objectList", default=None) + + +class Result(BaseModel): + data: Optional[ResultData] = None + """On success: contains objectList. On validation error: string error message.""" + + +class ObjectSignResponse(BaseModel): + """ + MMS service response format (Gateway transparent proxy, not standard ErrorResponse format) + """ + + error: Optional[str] = None + """Error message. 'OK' on success, error description on failure""" + + request_id: Optional[str] = None + """MMS request tracking ID""" + + result: Optional[Result] = None + + status: Optional[int] = None + """MMS status code. 0 = success, 2018 = validation error""" diff --git a/src/everos/types/v1/object_signed_info.py b/src/everos/types/v1/object_signed_info.py new file mode 100644 index 0000000..3162a70 --- /dev/null +++ b/src/everos/types/v1/object_signed_info.py @@ -0,0 +1,23 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from pydantic import Field as FieldInfo + +from ..._models import BaseModel + +__all__ = ["ObjectSignedInfo"] + + +class ObjectSignedInfo(BaseModel): + fields: Optional[Dict[str, str]] = None + """ + Form fields required for S3 POST upload (key, policy, x-amz-\\** headers, + Content-Type) + """ + + max_size: Optional[int] = FieldInfo(alias="maxSize", default=None) + """Maximum file size in bytes. image: 10MB, file: 100MB, video: 500MB""" + + url: Optional[str] = None + """Pre-signed S3 upload URL""" diff --git a/src/everos/types/v1/profile_item.py b/src/everos/types/v1/profile_item.py new file mode 100644 index 0000000..5eb6a05 --- /dev/null +++ b/src/everos/types/v1/profile_item.py @@ -0,0 +1,29 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from ..._models import BaseModel + +__all__ = ["ProfileItem"] + + +class ProfileItem(BaseModel): + """User profile item""" + + id: str + """MongoDB ObjectId as string""" + + group_id: Optional[str] = None + """Group ID""" + + memcell_count: Optional[int] = None + """Number of MemCells""" + + profile_data: Optional[Dict[str, object]] = None + """Profile data (explicit_info, implicit_traits)""" + + scenario: Optional[str] = None + """Scenario type: solo or team""" + + user_id: Optional[str] = None + """User ID""" diff --git a/src/everos/types/v1/raw_message_dto.py b/src/everos/types/v1/raw_message_dto.py new file mode 100644 index 0000000..d46ad6a --- /dev/null +++ b/src/everos/types/v1/raw_message_dto.py @@ -0,0 +1,43 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, List, Optional + +from ..._models import BaseModel + +__all__ = ["RawMessageDto"] + + +class RawMessageDto(BaseModel): + """Raw unprocessed message waiting for memory extraction""" + + id: str + """MongoDB ObjectId as string""" + + request_id: str + """Request ID""" + + content_items: Optional[List[Dict[str, object]]] = None + + created_at: Optional[str] = None + """ISO 8601 format""" + + group_id: Optional[str] = None + """Group ID""" + + message_id: Optional[str] = None + """Message ID""" + + sender_id: Optional[str] = None + """Sender ID""" + + sender_name: Optional[str] = None + """Sender name""" + + session_id: Optional[str] = None + """Session identifier""" + + timestamp: Optional[str] = None + """ISO 8601 format""" + + updated_at: Optional[str] = None + """ISO 8601 format""" diff --git a/src/everos/types/v1/search_memories_response.py b/src/everos/types/v1/search_memories_response.py new file mode 100644 index 0000000..f19d16b --- /dev/null +++ b/src/everos/types/v1/search_memories_response.py @@ -0,0 +1,13 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel +from .search_memories_response_data import SearchMemoriesResponseData + +__all__ = ["SearchMemoriesResponse"] + + +class SearchMemoriesResponse(BaseModel): + data: Optional[SearchMemoriesResponseData] = None + """Memory search result data""" diff --git a/src/everos/types/v1/memory_search_response.py b/src/everos/types/v1/search_memories_response_data.py similarity index 73% rename from src/everos/types/v1/memory_search_response.py rename to src/everos/types/v1/search_memories_response_data.py index 8f3a590..18c9bea 100644 --- a/src/everos/types/v1/memory_search_response.py +++ b/src/everos/types/v1/search_memories_response_data.py @@ -4,22 +4,21 @@ from datetime import datetime from ..._models import BaseModel +from .raw_message_dto import RawMessageDto __all__ = [ - "MemorySearchResponse", - "Data", - "DataQuery", - "DataAgentMemory", - "DataAgentMemoryCase", - "DataAgentMemorySkill", - "DataEpisode", - "DataEpisodeAtomicFact", - "DataProfile", - "DataRawMessage", + "SearchMemoriesResponseData", + "Query", + "AgentMemory", + "AgentMemoryCase", + "AgentMemorySkill", + "Episode", + "EpisodeAtomicFact", + "Profile", ] -class DataQuery(BaseModel): +class Query(BaseModel): method: str """Retrieval method used""" @@ -30,7 +29,7 @@ class DataQuery(BaseModel): """Filters applied""" -class DataAgentMemoryCase(BaseModel): +class AgentMemoryCase(BaseModel): """Agent case search result with relevance score""" id: str @@ -67,7 +66,7 @@ class DataAgentMemoryCase(BaseModel): """User ID""" -class DataAgentMemorySkill(BaseModel): +class AgentMemorySkill(BaseModel): """Agent skill search result with relevance score""" id: str @@ -104,17 +103,17 @@ class DataAgentMemorySkill(BaseModel): """User ID""" -class DataAgentMemory(BaseModel): +class AgentMemory(BaseModel): """Agent memory search results (cases + skills)""" - cases: Optional[List[DataAgentMemoryCase]] = None + cases: Optional[List[AgentMemoryCase]] = None """Agent case search results""" - skills: Optional[List[DataAgentMemorySkill]] = None + skills: Optional[List[AgentMemorySkill]] = None """Agent skill search results""" -class DataEpisodeAtomicFact(BaseModel): +class EpisodeAtomicFact(BaseModel): """Atomic fact expanded from an episode""" id: str @@ -154,13 +153,13 @@ class DataEpisodeAtomicFact(BaseModel): """User ID""" -class DataEpisode(BaseModel): +class Episode(BaseModel): """Episode search result with relevance score""" id: str """MongoDB ObjectId as string""" - atomic_facts: Optional[List[DataEpisodeAtomicFact]] = None + atomic_facts: Optional[List[EpisodeAtomicFact]] = None """Atomic facts expanded from this episode""" episode: Optional[str] = None @@ -200,7 +199,7 @@ class DataEpisode(BaseModel): """Owner user ID""" -class DataProfile(BaseModel): +class Profile(BaseModel): """Profile search result with relevance score""" id: str @@ -225,63 +224,22 @@ class DataProfile(BaseModel): """User ID""" -class DataRawMessage(BaseModel): - """Raw unprocessed message waiting for memory extraction""" - - id: str - """MongoDB ObjectId as string""" - - request_id: str - """Request ID""" - - content_items: Optional[List[Dict[str, object]]] = None - - created_at: Optional[str] = None - """ISO 8601 format""" - - group_id: Optional[str] = None - """Group ID""" - - message_id: Optional[str] = None - """Message ID""" - - sender_id: Optional[str] = None - """Sender ID""" - - sender_name: Optional[str] = None - """Sender name""" - - session_id: Optional[str] = None - """Session identifier""" - - timestamp: Optional[str] = None - """ISO 8601 format""" - - updated_at: Optional[str] = None - """ISO 8601 format""" - - -class Data(BaseModel): +class SearchMemoriesResponseData(BaseModel): """Memory search result data""" - query: DataQuery + query: Query - agent_memory: Optional[DataAgentMemory] = None + agent_memory: Optional[AgentMemory] = None """Agent memory search results (cases + skills)""" - episodes: Optional[List[DataEpisode]] = None + episodes: Optional[List[Episode]] = None """Episodic memory search results""" original_data: Optional[Dict[str, object]] = None """Original data (if include_original_data=true)""" - profiles: Optional[List[DataProfile]] = None + profiles: Optional[List[Profile]] = None """Profile search results""" - raw_messages: Optional[List[DataRawMessage]] = None + raw_messages: Optional[List[RawMessageDto]] = None """Raw unprocessed messages (pending)""" - - -class MemorySearchResponse(BaseModel): - data: Optional[Data] = None - """Memory search result data""" diff --git a/src/everos/types/v1/sender_api_response.py b/src/everos/types/v1/sender_api_response.py index 9581b54..35823ff 100644 --- a/src/everos/types/v1/sender_api_response.py +++ b/src/everos/types/v1/sender_api_response.py @@ -3,23 +3,10 @@ from typing import Optional from ..._models import BaseModel +from .sender_response import SenderResponse -__all__ = ["SenderAPIResponse", "Data"] - - -class Data(BaseModel): - created_at: str - """Creation time (ISO 8601)""" - - sender_id: str - """Sender identifier""" - - updated_at: str - """Last update time (ISO 8601)""" - - name: Optional[str] = None - """Sender display name""" +__all__ = ["SenderAPIResponse"] class SenderAPIResponse(BaseModel): - data: Optional[Data] = None + data: Optional[SenderResponse] = None diff --git a/src/everos/types/v1/sender_create_or_update_params.py b/src/everos/types/v1/sender_create_params.py similarity index 76% rename from src/everos/types/v1/sender_create_or_update_params.py rename to src/everos/types/v1/sender_create_params.py index e0439b1..ce10f5b 100644 --- a/src/everos/types/v1/sender_create_or_update_params.py +++ b/src/everos/types/v1/sender_create_params.py @@ -5,10 +5,10 @@ from typing import Optional from typing_extensions import Required, TypedDict -__all__ = ["SenderCreateOrUpdateParams"] +__all__ = ["SenderCreateParams"] -class SenderCreateOrUpdateParams(TypedDict, total=False): +class SenderCreateParams(TypedDict, total=False): sender_id: Required[str] """Sender identifier (unique)""" diff --git a/src/everos/types/v1/sender_update_params.py b/src/everos/types/v1/sender_patch_params.py similarity index 75% rename from src/everos/types/v1/sender_update_params.py rename to src/everos/types/v1/sender_patch_params.py index 9fc7ffc..7fd428e 100644 --- a/src/everos/types/v1/sender_update_params.py +++ b/src/everos/types/v1/sender_patch_params.py @@ -5,9 +5,9 @@ from typing import Optional from typing_extensions import TypedDict -__all__ = ["SenderUpdateParams"] +__all__ = ["SenderPatchParams"] -class SenderUpdateParams(TypedDict, total=False): +class SenderPatchParams(TypedDict, total=False): name: Optional[str] """New sender display name""" diff --git a/src/everos/types/v1/sender_response.py b/src/everos/types/v1/sender_response.py new file mode 100644 index 0000000..6641b8e --- /dev/null +++ b/src/everos/types/v1/sender_response.py @@ -0,0 +1,21 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Optional + +from ..._models import BaseModel + +__all__ = ["SenderResponse"] + + +class SenderResponse(BaseModel): + created_at: str + """Creation time (ISO 8601)""" + + sender_id: str + """Sender identifier""" + + updated_at: str + """Last update time (ISO 8601)""" + + name: Optional[str] = None + """Sender display name""" diff --git a/src/everos/types/v1/setting_update_params.py b/src/everos/types/v1/setting_update_params.py index dcdd093..f44ccf2 100644 --- a/src/everos/types/v1/setting_update_params.py +++ b/src/everos/types/v1/setting_update_params.py @@ -2,12 +2,12 @@ from __future__ import annotations -from typing import Dict, Optional +from typing import Optional from typing_extensions import Literal, TypedDict -from .llm_provider_config_param import LlmProviderConfigParam +from .llm_custom_setting_param import LlmCustomSettingParam -__all__ = ["SettingUpdateParams", "LlmCustomSetting"] +__all__ = ["SettingUpdateParams"] class SettingUpdateParams(TypedDict, total=False): @@ -17,7 +17,7 @@ class SettingUpdateParams(TypedDict, total=False): extraction_mode: Optional[Literal["default", "pro"]] """Extraction mode""" - llm_custom_setting: Optional[LlmCustomSetting] + llm_custom_setting: Optional[LlmCustomSettingParam] """LLM custom settings for algorithm control""" offline_profile_extraction_interval: Optional[int] @@ -25,16 +25,3 @@ class SettingUpdateParams(TypedDict, total=False): timezone: Optional[str] """IANA timezone identifier""" - - -class LlmCustomSetting(TypedDict, total=False): - """LLM custom settings for algorithm control""" - - boundary: Optional[LlmProviderConfigParam] - """LLM config for boundary detection (fast, cheap model recommended)""" - - extra: Optional[Dict[str, object]] - """Additional task-specific LLM configurations""" - - extraction: Optional[LlmProviderConfigParam] - """LLM config for memory extraction (high quality model recommended)""" diff --git a/src/everos/types/v1/settings_api_response.py b/src/everos/types/v1/settings_api_response.py index bde4ea6..2e77df5 100644 --- a/src/everos/types/v1/settings_api_response.py +++ b/src/everos/types/v1/settings_api_response.py @@ -1,34 +1,12 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -from typing import Dict, Optional +from typing import Optional from ..._models import BaseModel +from .settings_response import SettingsResponse -__all__ = ["SettingsAPIResponse", "Data"] - - -class Data(BaseModel): - boundary_detection_timeout: int - """MemCell auto-flush idle timeout in seconds""" - - created_at: str - """Creation time (ISO 8601)""" - - extraction_mode: str - """Extraction mode""" - - offline_profile_extraction_interval: int - """Offline profile extraction interval in seconds""" - - timezone: str - """IANA timezone identifier""" - - updated_at: str - """Last update time (ISO 8601)""" - - llm_custom_setting: Optional[Dict[str, object]] = None - """LLM custom settings (serialized)""" +__all__ = ["SettingsAPIResponse"] class SettingsAPIResponse(BaseModel): - data: Optional[Data] = None + data: Optional[SettingsResponse] = None diff --git a/src/everos/types/v1/settings_response.py b/src/everos/types/v1/settings_response.py new file mode 100644 index 0000000..7b6ed13 --- /dev/null +++ b/src/everos/types/v1/settings_response.py @@ -0,0 +1,30 @@ +# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +from typing import Dict, Optional + +from ..._models import BaseModel + +__all__ = ["SettingsResponse"] + + +class SettingsResponse(BaseModel): + boundary_detection_timeout: int + """MemCell auto-flush idle timeout in seconds""" + + created_at: str + """Creation time (ISO 8601)""" + + extraction_mode: str + """Extraction mode""" + + offline_profile_extraction_interval: int + """Offline profile extraction interval in seconds""" + + timezone: str + """IANA timezone identifier""" + + updated_at: str + """Last update time (ISO 8601)""" + + llm_custom_setting: Optional[Dict[str, object]] = None + """LLM custom settings (serialized)""" diff --git a/src/everos/types/v1_query_task_status_response.py b/src/everos/types/v1/task_status_result.py similarity index 61% rename from src/everos/types/v1_query_task_status_response.py rename to src/everos/types/v1/task_status_result.py index 6a02074..db8827d 100644 --- a/src/everos/types/v1_query_task_status_response.py +++ b/src/everos/types/v1/task_status_result.py @@ -2,18 +2,14 @@ from typing_extensions import Literal -from .._models import BaseModel +from ..._models import BaseModel -__all__ = ["V1QueryTaskStatusResponse", "Data"] +__all__ = ["TaskStatusResult"] -class Data(BaseModel): +class TaskStatusResult(BaseModel): status: Literal["processing", "success", "failed"] """Task status""" task_id: str """Async task tracking ID""" - - -class V1QueryTaskStatusResponse(BaseModel): - data: Data diff --git a/tests/api_resources/v1/memories/test_agent.py b/tests/api_resources/v1/memories/test_agent.py index 5ffc615..329e1c5 100644 --- a/tests/api_resources/v1/memories/test_agent.py +++ b/tests/api_resources/v1/memories/test_agent.py @@ -7,7 +7,7 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import AddResponse, FlushResponse @@ -19,8 +19,8 @@ class TestAgent: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create(self, client: Everos) -> None: - agent = client.v1.memories.agent.create( + def test_method_add(self, client: EverOS) -> None: + agent = client.v1.memories.agent.add( messages=[ { "role": "user", @@ -33,8 +33,8 @@ def test_method_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_with_all_params(self, client: Everos) -> None: - agent = client.v1.memories.agent.create( + def test_method_add_with_all_params(self, client: EverOS) -> None: + agent = client.v1.memories.agent.add( messages=[ { "role": "user", @@ -62,8 +62,8 @@ def test_method_create_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create(self, client: Everos) -> None: - response = client.v1.memories.agent.with_raw_response.create( + def test_raw_response_add(self, client: EverOS) -> None: + response = client.v1.memories.agent.with_raw_response.add( messages=[ { "role": "user", @@ -80,8 +80,8 @@ def test_raw_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create(self, client: Everos) -> None: - with client.v1.memories.agent.with_streaming_response.create( + def test_streaming_response_add(self, client: EverOS) -> None: + with client.v1.memories.agent.with_streaming_response.add( messages=[ { "role": "user", @@ -100,7 +100,7 @@ def test_streaming_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_flush(self, client: Everos) -> None: + def test_method_flush(self, client: EverOS) -> None: agent = client.v1.memories.agent.flush( user_id="user_id", ) @@ -108,7 +108,7 @@ def test_method_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_flush_with_all_params(self, client: Everos) -> None: + def test_method_flush_with_all_params(self, client: EverOS) -> None: agent = client.v1.memories.agent.flush( user_id="user_id", session_id="session_id", @@ -117,7 +117,7 @@ def test_method_flush_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_flush(self, client: Everos) -> None: + def test_raw_response_flush(self, client: EverOS) -> None: response = client.v1.memories.agent.with_raw_response.flush( user_id="user_id", ) @@ -129,7 +129,7 @@ def test_raw_response_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_flush(self, client: Everos) -> None: + def test_streaming_response_flush(self, client: EverOS) -> None: with client.v1.memories.agent.with_streaming_response.flush( user_id="user_id", ) as response: @@ -149,8 +149,8 @@ class TestAsyncAgent: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create(self, async_client: AsyncEveros) -> None: - agent = await async_client.v1.memories.agent.create( + async def test_method_add(self, async_client: AsyncEverOS) -> None: + agent = await async_client.v1.memories.agent.add( messages=[ { "role": "user", @@ -163,8 +163,8 @@ async def test_method_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> None: - agent = await async_client.v1.memories.agent.create( + async def test_method_add_with_all_params(self, async_client: AsyncEverOS) -> None: + agent = await async_client.v1.memories.agent.add( messages=[ { "role": "user", @@ -192,8 +192,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.memories.agent.with_raw_response.create( + async def test_raw_response_add(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.memories.agent.with_raw_response.add( messages=[ { "role": "user", @@ -210,8 +210,8 @@ async def test_raw_response_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create(self, async_client: AsyncEveros) -> None: - async with async_client.v1.memories.agent.with_streaming_response.create( + async def test_streaming_response_add(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.memories.agent.with_streaming_response.add( messages=[ { "role": "user", @@ -230,7 +230,7 @@ async def test_streaming_response_create(self, async_client: AsyncEveros) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_flush(self, async_client: AsyncEveros) -> None: + async def test_method_flush(self, async_client: AsyncEverOS) -> None: agent = await async_client.v1.memories.agent.flush( user_id="user_id", ) @@ -238,7 +238,7 @@ async def test_method_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_flush_with_all_params(self, async_client: AsyncEveros) -> None: + async def test_method_flush_with_all_params(self, async_client: AsyncEverOS) -> None: agent = await async_client.v1.memories.agent.flush( user_id="user_id", session_id="session_id", @@ -247,7 +247,7 @@ async def test_method_flush_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: + async def test_raw_response_flush(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.memories.agent.with_raw_response.flush( user_id="user_id", ) @@ -259,7 +259,7 @@ async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_flush(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_flush(self, async_client: AsyncEverOS) -> None: async with async_client.v1.memories.agent.with_streaming_response.flush( user_id="user_id", ) as response: diff --git a/tests/api_resources/v1/memories/test_group.py b/tests/api_resources/v1/memories/test_group.py index 5fd97f5..73b0d08 100644 --- a/tests/api_resources/v1/memories/test_group.py +++ b/tests/api_resources/v1/memories/test_group.py @@ -7,7 +7,7 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import AddResponse, FlushResponse @@ -19,8 +19,8 @@ class TestGroup: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create(self, client: Everos) -> None: - group = client.v1.memories.group.create( + def test_method_add(self, client: EverOS) -> None: + group = client.v1.memories.group.add( group_id="group_id", messages=[ { @@ -35,8 +35,8 @@ def test_method_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_with_all_params(self, client: Everos) -> None: - group = client.v1.memories.group.create( + def test_method_add_with_all_params(self, client: EverOS) -> None: + group = client.v1.memories.group.add( group_id="group_id", messages=[ { @@ -55,8 +55,8 @@ def test_method_create_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create(self, client: Everos) -> None: - response = client.v1.memories.group.with_raw_response.create( + def test_raw_response_add(self, client: EverOS) -> None: + response = client.v1.memories.group.with_raw_response.add( group_id="group_id", messages=[ { @@ -75,8 +75,8 @@ def test_raw_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create(self, client: Everos) -> None: - with client.v1.memories.group.with_streaming_response.create( + def test_streaming_response_add(self, client: EverOS) -> None: + with client.v1.memories.group.with_streaming_response.add( group_id="group_id", messages=[ { @@ -97,7 +97,7 @@ def test_streaming_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_flush(self, client: Everos) -> None: + def test_method_flush(self, client: EverOS) -> None: group = client.v1.memories.group.flush( group_id="group_id", ) @@ -105,7 +105,7 @@ def test_method_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_flush(self, client: Everos) -> None: + def test_raw_response_flush(self, client: EverOS) -> None: response = client.v1.memories.group.with_raw_response.flush( group_id="group_id", ) @@ -117,7 +117,7 @@ def test_raw_response_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_flush(self, client: Everos) -> None: + def test_streaming_response_flush(self, client: EverOS) -> None: with client.v1.memories.group.with_streaming_response.flush( group_id="group_id", ) as response: @@ -137,8 +137,8 @@ class TestAsyncGroup: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.memories.group.create( + async def test_method_add(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.memories.group.add( group_id="group_id", messages=[ { @@ -153,8 +153,8 @@ async def test_method_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.memories.group.create( + async def test_method_add_with_all_params(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.memories.group.add( group_id="group_id", messages=[ { @@ -173,8 +173,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.memories.group.with_raw_response.create( + async def test_raw_response_add(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.memories.group.with_raw_response.add( group_id="group_id", messages=[ { @@ -193,8 +193,8 @@ async def test_raw_response_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create(self, async_client: AsyncEveros) -> None: - async with async_client.v1.memories.group.with_streaming_response.create( + async def test_streaming_response_add(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.memories.group.with_streaming_response.add( group_id="group_id", messages=[ { @@ -215,7 +215,7 @@ async def test_streaming_response_create(self, async_client: AsyncEveros) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_flush(self, async_client: AsyncEveros) -> None: + async def test_method_flush(self, async_client: AsyncEverOS) -> None: group = await async_client.v1.memories.group.flush( group_id="group_id", ) @@ -223,7 +223,7 @@ async def test_method_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: + async def test_raw_response_flush(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.memories.group.with_raw_response.flush( group_id="group_id", ) @@ -235,7 +235,7 @@ async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_flush(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_flush(self, async_client: AsyncEverOS) -> None: async with async_client.v1.memories.group.with_streaming_response.flush( group_id="group_id", ) as response: diff --git a/tests/api_resources/v1/test_groups.py b/tests/api_resources/v1/test_groups.py index 32c00ef..c02783f 100644 --- a/tests/api_resources/v1/test_groups.py +++ b/tests/api_resources/v1/test_groups.py @@ -7,7 +7,7 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import GroupAPIResponse @@ -19,7 +19,51 @@ class TestGroups: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_retrieve(self, client: Everos) -> None: + def test_method_create(self, client: EverOS) -> None: + group = client.v1.groups.create( + group_id="group_abc", + ) + assert_matches_type(GroupAPIResponse, group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: EverOS) -> None: + group = client.v1.groups.create( + group_id="group_abc", + description="Weekly sync on Project X", + name="Project Discussion", + ) + assert_matches_type(GroupAPIResponse, group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: EverOS) -> None: + response = client.v1.groups.with_raw_response.create( + group_id="group_abc", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + group = response.parse() + assert_matches_type(GroupAPIResponse, group, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: EverOS) -> None: + with client.v1.groups.with_streaming_response.create( + group_id="group_abc", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + group = response.parse() + assert_matches_type(GroupAPIResponse, group, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: EverOS) -> None: group = client.v1.groups.retrieve( "group_abc", ) @@ -27,7 +71,7 @@ def test_method_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_retrieve(self, client: Everos) -> None: + def test_raw_response_retrieve(self, client: EverOS) -> None: response = client.v1.groups.with_raw_response.retrieve( "group_abc", ) @@ -39,7 +83,7 @@ def test_raw_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_retrieve(self, client: Everos) -> None: + def test_streaming_response_retrieve(self, client: EverOS) -> None: with client.v1.groups.with_streaming_response.retrieve( "group_abc", ) as response: @@ -53,7 +97,7 @@ def test_streaming_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_retrieve(self, client: Everos) -> None: + def test_path_params_retrieve(self, client: EverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): client.v1.groups.with_raw_response.retrieve( "", @@ -61,16 +105,16 @@ def test_path_params_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update(self, client: Everos) -> None: - group = client.v1.groups.update( + def test_method_patch(self, client: EverOS) -> None: + group = client.v1.groups.patch( group_id="group_abc", ) assert_matches_type(GroupAPIResponse, group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update_with_all_params(self, client: Everos) -> None: - group = client.v1.groups.update( + def test_method_patch_with_all_params(self, client: EverOS) -> None: + group = client.v1.groups.patch( group_id="group_abc", description="description", name="name", @@ -79,8 +123,8 @@ def test_method_update_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_update(self, client: Everos) -> None: - response = client.v1.groups.with_raw_response.update( + def test_raw_response_patch(self, client: EverOS) -> None: + response = client.v1.groups.with_raw_response.patch( group_id="group_abc", ) @@ -91,8 +135,8 @@ def test_raw_response_update(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_update(self, client: Everos) -> None: - with client.v1.groups.with_streaming_response.update( + def test_streaming_response_patch(self, client: EverOS) -> None: + with client.v1.groups.with_streaming_response.patch( group_id="group_abc", ) as response: assert not response.is_closed @@ -105,24 +149,30 @@ def test_streaming_response_update(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_update(self, client: Everos) -> None: + def test_path_params_patch(self, client: EverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): - client.v1.groups.with_raw_response.update( + client.v1.groups.with_raw_response.patch( group_id="", ) + +class TestAsyncGroups: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_or_update(self, client: Everos) -> None: - group = client.v1.groups.create_or_update( + async def test_method_create(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.groups.create( group_id="group_abc", ) assert_matches_type(GroupAPIResponse, group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_or_update_with_all_params(self, client: Everos) -> None: - group = client.v1.groups.create_or_update( + async def test_method_create_with_all_params(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.groups.create( group_id="group_abc", description="Weekly sync on Project X", name="Project Discussion", @@ -131,39 +181,33 @@ def test_method_create_or_update_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create_or_update(self, client: Everos) -> None: - response = client.v1.groups.with_raw_response.create_or_update( + async def test_raw_response_create(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.groups.with_raw_response.create( group_id="group_abc", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - group = response.parse() + group = await response.parse() assert_matches_type(GroupAPIResponse, group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create_or_update(self, client: Everos) -> None: - with client.v1.groups.with_streaming_response.create_or_update( + async def test_streaming_response_create(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.groups.with_streaming_response.create( group_id="group_abc", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - group = response.parse() + group = await response.parse() assert_matches_type(GroupAPIResponse, group, path=["response"]) assert cast(Any, response.is_closed) is True - -class TestAsyncGroups: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_retrieve(self, async_client: AsyncEveros) -> None: + async def test_method_retrieve(self, async_client: AsyncEverOS) -> None: group = await async_client.v1.groups.retrieve( "group_abc", ) @@ -171,7 +215,7 @@ async def test_method_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.groups.with_raw_response.retrieve( "group_abc", ) @@ -183,7 +227,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncEverOS) -> None: async with async_client.v1.groups.with_streaming_response.retrieve( "group_abc", ) as response: @@ -197,7 +241,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> N @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_retrieve(self, async_client: AsyncEveros) -> None: + async def test_path_params_retrieve(self, async_client: AsyncEverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): await async_client.v1.groups.with_raw_response.retrieve( "", @@ -205,16 +249,16 @@ async def test_path_params_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.groups.update( + async def test_method_patch(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.groups.patch( group_id="group_abc", ) assert_matches_type(GroupAPIResponse, group, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.groups.update( + async def test_method_patch_with_all_params(self, async_client: AsyncEverOS) -> None: + group = await async_client.v1.groups.patch( group_id="group_abc", description="description", name="name", @@ -223,8 +267,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_update(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.groups.with_raw_response.update( + async def test_raw_response_patch(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.groups.with_raw_response.patch( group_id="group_abc", ) @@ -235,8 +279,8 @@ async def test_raw_response_update(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_update(self, async_client: AsyncEveros) -> None: - async with async_client.v1.groups.with_streaming_response.update( + async def test_streaming_response_patch(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.groups.with_streaming_response.patch( group_id="group_abc", ) as response: assert not response.is_closed @@ -249,52 +293,8 @@ async def test_streaming_response_update(self, async_client: AsyncEveros) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_update(self, async_client: AsyncEveros) -> None: + async def test_path_params_patch(self, async_client: AsyncEverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `group_id` but received ''"): - await async_client.v1.groups.with_raw_response.update( + await async_client.v1.groups.with_raw_response.patch( group_id="", ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_or_update(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.groups.create_or_update( - group_id="group_abc", - ) - assert_matches_type(GroupAPIResponse, group, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_or_update_with_all_params(self, async_client: AsyncEveros) -> None: - group = await async_client.v1.groups.create_or_update( - group_id="group_abc", - description="Weekly sync on Project X", - name="Project Discussion", - ) - assert_matches_type(GroupAPIResponse, group, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create_or_update(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.groups.with_raw_response.create_or_update( - group_id="group_abc", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - group = await response.parse() - assert_matches_type(GroupAPIResponse, group, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create_or_update(self, async_client: AsyncEveros) -> None: - async with async_client.v1.groups.with_streaming_response.create_or_update( - group_id="group_abc", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - group = await response.parse() - assert_matches_type(GroupAPIResponse, group, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/v1/test_memories.py b/tests/api_resources/v1/test_memories.py index 37f5e8c..aafc0fb 100644 --- a/tests/api_resources/v1/test_memories.py +++ b/tests/api_resources/v1/test_memories.py @@ -7,13 +7,13 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import ( AddResponse, FlushResponse, - MemoryGetResponse, - MemorySearchResponse, + GetMemoriesResponse, + SearchMemoriesResponse, ) base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -24,8 +24,48 @@ class TestMemories: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create(self, client: Everos) -> None: - memory = client.v1.memories.create( + def test_method_delete(self, client: EverOS) -> None: + memory = client.v1.memories.delete() + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_delete_with_all_params(self, client: EverOS) -> None: + memory = client.v1.memories.delete( + group_id="group_id", + memory_id="memory_id", + sender_id="sender_id", + session_id="session_id", + user_id="user_id", + ) + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_delete(self, client: EverOS) -> None: + response = client.v1.memories.with_raw_response.delete() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = response.parse() + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_delete(self, client: EverOS) -> None: + with client.v1.memories.with_streaming_response.delete() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = response.parse() + assert memory is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_add(self, client: EverOS) -> None: + memory = client.v1.memories.add( messages=[ { "content": "x", @@ -39,8 +79,8 @@ def test_method_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_with_all_params(self, client: Everos) -> None: - memory = client.v1.memories.create( + def test_method_add_with_all_params(self, client: EverOS) -> None: + memory = client.v1.memories.add( messages=[ { "content": "x", @@ -57,8 +97,8 @@ def test_method_create_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create(self, client: Everos) -> None: - response = client.v1.memories.with_raw_response.create( + def test_raw_response_add(self, client: EverOS) -> None: + response = client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -76,8 +116,8 @@ def test_raw_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create(self, client: Everos) -> None: - with client.v1.memories.with_streaming_response.create( + def test_streaming_response_add(self, client: EverOS) -> None: + with client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -97,47 +137,7 @@ def test_streaming_response_create(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_delete(self, client: Everos) -> None: - memory = client.v1.memories.delete() - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_delete_with_all_params(self, client: Everos) -> None: - memory = client.v1.memories.delete( - group_id="group_id", - memory_id="memory_id", - sender_id="sender_id", - session_id="session_id", - user_id="user_id", - ) - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_raw_response_delete(self, client: Everos) -> None: - response = client.v1.memories.with_raw_response.delete() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = response.parse() - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_streaming_response_delete(self, client: Everos) -> None: - with client.v1.memories.with_streaming_response.delete() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = response.parse() - assert memory is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - def test_method_flush(self, client: Everos) -> None: + def test_method_flush(self, client: EverOS) -> None: memory = client.v1.memories.flush( user_id="user_id", ) @@ -145,7 +145,7 @@ def test_method_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_flush_with_all_params(self, client: Everos) -> None: + def test_method_flush_with_all_params(self, client: EverOS) -> None: memory = client.v1.memories.flush( user_id="user_id", session_id="session_id", @@ -154,7 +154,7 @@ def test_method_flush_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_flush(self, client: Everos) -> None: + def test_raw_response_flush(self, client: EverOS) -> None: response = client.v1.memories.with_raw_response.flush( user_id="user_id", ) @@ -166,7 +166,7 @@ def test_raw_response_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_flush(self, client: Everos) -> None: + def test_streaming_response_flush(self, client: EverOS) -> None: with client.v1.memories.with_streaming_response.flush( user_id="user_id", ) as response: @@ -180,16 +180,16 @@ def test_streaming_response_flush(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_get(self, client: Everos) -> None: + def test_method_get(self, client: EverOS) -> None: memory = client.v1.memories.get( filters={"foo": "bar"}, memory_type="episodic_memory", ) - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_get_with_all_params(self, client: Everos) -> None: + def test_method_get_with_all_params(self, client: EverOS) -> None: memory = client.v1.memories.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -198,11 +198,11 @@ def test_method_get_with_all_params(self, client: Everos) -> None: rank_by="rank_by", rank_order="asc", ) - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_get(self, client: Everos) -> None: + def test_raw_response_get(self, client: EverOS) -> None: response = client.v1.memories.with_raw_response.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -211,11 +211,11 @@ def test_raw_response_get(self, client: Everos) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_get(self, client: Everos) -> None: + def test_streaming_response_get(self, client: EverOS) -> None: with client.v1.memories.with_streaming_response.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -224,22 +224,22 @@ def test_streaming_response_get(self, client: Everos) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_search(self, client: Everos) -> None: + def test_method_search(self, client: EverOS) -> None: memory = client.v1.memories.search( filters={"foo": "bar"}, query="What did Alice say about the project?", ) - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_search_with_all_params(self, client: Everos) -> None: + def test_method_search_with_all_params(self, client: EverOS) -> None: memory = client.v1.memories.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -249,11 +249,11 @@ def test_method_search_with_all_params(self, client: Everos) -> None: radius=0, top_k=-1, ) - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_search(self, client: Everos) -> None: + def test_raw_response_search(self, client: EverOS) -> None: response = client.v1.memories.with_raw_response.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -262,11 +262,11 @@ def test_raw_response_search(self, client: Everos) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_search(self, client: Everos) -> None: + def test_streaming_response_search(self, client: EverOS) -> None: with client.v1.memories.with_streaming_response.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -275,7 +275,7 @@ def test_streaming_response_search(self, client: Everos) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = response.parse() - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @@ -287,8 +287,48 @@ class TestAsyncMemories: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create(self, async_client: AsyncEveros) -> None: - memory = await async_client.v1.memories.create( + async def test_method_delete(self, async_client: AsyncEverOS) -> None: + memory = await async_client.v1.memories.delete() + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_delete_with_all_params(self, async_client: AsyncEverOS) -> None: + memory = await async_client.v1.memories.delete( + group_id="group_id", + memory_id="memory_id", + sender_id="sender_id", + session_id="session_id", + user_id="user_id", + ) + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_raw_response_delete(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.memories.with_raw_response.delete() + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + memory = await response.parse() + assert memory is None + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_streaming_response_delete(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.memories.with_streaming_response.delete() as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + memory = await response.parse() + assert memory is None + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + async def test_method_add(self, async_client: AsyncEverOS) -> None: + memory = await async_client.v1.memories.add( messages=[ { "content": "x", @@ -302,8 +342,8 @@ async def test_method_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> None: - memory = await async_client.v1.memories.create( + async def test_method_add_with_all_params(self, async_client: AsyncEverOS) -> None: + memory = await async_client.v1.memories.add( messages=[ { "content": "x", @@ -320,8 +360,8 @@ async def test_method_create_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_create(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.memories.with_raw_response.create( + async def test_raw_response_add(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -339,8 +379,8 @@ async def test_raw_response_create(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_create(self, async_client: AsyncEveros) -> None: - async with async_client.v1.memories.with_streaming_response.create( + async def test_streaming_response_add(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -360,47 +400,7 @@ async def test_streaming_response_create(self, async_client: AsyncEveros) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_delete(self, async_client: AsyncEveros) -> None: - memory = await async_client.v1.memories.delete() - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_delete_with_all_params(self, async_client: AsyncEveros) -> None: - memory = await async_client.v1.memories.delete( - group_id="group_id", - memory_id="memory_id", - sender_id="sender_id", - session_id="session_id", - user_id="user_id", - ) - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_delete(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.memories.with_raw_response.delete() - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - memory = await response.parse() - assert memory is None - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_delete(self, async_client: AsyncEveros) -> None: - async with async_client.v1.memories.with_streaming_response.delete() as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - memory = await response.parse() - assert memory is None - - assert cast(Any, response.is_closed) is True - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_flush(self, async_client: AsyncEveros) -> None: + async def test_method_flush(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.flush( user_id="user_id", ) @@ -408,7 +408,7 @@ async def test_method_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_flush_with_all_params(self, async_client: AsyncEveros) -> None: + async def test_method_flush_with_all_params(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.flush( user_id="user_id", session_id="session_id", @@ -417,7 +417,7 @@ async def test_method_flush_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: + async def test_raw_response_flush(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.memories.with_raw_response.flush( user_id="user_id", ) @@ -429,7 +429,7 @@ async def test_raw_response_flush(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_flush(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_flush(self, async_client: AsyncEverOS) -> None: async with async_client.v1.memories.with_streaming_response.flush( user_id="user_id", ) as response: @@ -443,16 +443,16 @@ async def test_streaming_response_flush(self, async_client: AsyncEveros) -> None @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_get(self, async_client: AsyncEveros) -> None: + async def test_method_get(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.get( filters={"foo": "bar"}, memory_type="episodic_memory", ) - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_get_with_all_params(self, async_client: AsyncEveros) -> None: + async def test_method_get_with_all_params(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -461,11 +461,11 @@ async def test_method_get_with_all_params(self, async_client: AsyncEveros) -> No rank_by="rank_by", rank_order="asc", ) - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_get(self, async_client: AsyncEveros) -> None: + async def test_raw_response_get(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.memories.with_raw_response.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -474,11 +474,11 @@ async def test_raw_response_get(self, async_client: AsyncEveros) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_get(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_get(self, async_client: AsyncEverOS) -> None: async with async_client.v1.memories.with_streaming_response.get( filters={"foo": "bar"}, memory_type="episodic_memory", @@ -487,22 +487,22 @@ async def test_streaming_response_get(self, async_client: AsyncEveros) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemoryGetResponse, memory, path=["response"]) + assert_matches_type(GetMemoriesResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_search(self, async_client: AsyncEveros) -> None: + async def test_method_search(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.search( filters={"foo": "bar"}, query="What did Alice say about the project?", ) - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_search_with_all_params(self, async_client: AsyncEveros) -> None: + async def test_method_search_with_all_params(self, async_client: AsyncEverOS) -> None: memory = await async_client.v1.memories.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -512,11 +512,11 @@ async def test_method_search_with_all_params(self, async_client: AsyncEveros) -> radius=0, top_k=-1, ) - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_search(self, async_client: AsyncEveros) -> None: + async def test_raw_response_search(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.memories.with_raw_response.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -525,11 +525,11 @@ async def test_raw_response_search(self, async_client: AsyncEveros) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_search(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_search(self, async_client: AsyncEverOS) -> None: async with async_client.v1.memories.with_streaming_response.search( filters={"foo": "bar"}, query="What did Alice say about the project?", @@ -538,6 +538,6 @@ async def test_streaming_response_search(self, async_client: AsyncEveros) -> Non assert response.http_request.headers.get("X-Stainless-Lang") == "python" memory = await response.parse() - assert_matches_type(MemorySearchResponse, memory, path=["response"]) + assert_matches_type(SearchMemoriesResponse, memory, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/v1/test_object.py b/tests/api_resources/v1/test_object.py index 0f43a2c..583717b 100644 --- a/tests/api_resources/v1/test_object.py +++ b/tests/api_resources/v1/test_object.py @@ -7,9 +7,9 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type -from everos.types.v1 import ObjectGetPresignedURLResponse +from everos.types.v1 import ObjectSignResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") @@ -19,8 +19,8 @@ class TestObject: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_get_presigned_url(self, client: Everos) -> None: - object_ = client.v1.object.get_presigned_url( + def test_method_sign(self, client: EverOS) -> None: + object_ = client.v1.object.sign( object_list=[ { "file_id": "file_abc123", @@ -29,12 +29,12 @@ def test_method_get_presigned_url(self, client: Everos) -> None: } ], ) - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_get_presigned_url(self, client: Everos) -> None: - response = client.v1.object.with_raw_response.get_presigned_url( + def test_raw_response_sign(self, client: EverOS) -> None: + response = client.v1.object.with_raw_response.sign( object_list=[ { "file_id": "file_abc123", @@ -47,12 +47,12 @@ def test_raw_response_get_presigned_url(self, client: Everos) -> None: assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" object_ = response.parse() - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_get_presigned_url(self, client: Everos) -> None: - with client.v1.object.with_streaming_response.get_presigned_url( + def test_streaming_response_sign(self, client: EverOS) -> None: + with client.v1.object.with_streaming_response.sign( object_list=[ { "file_id": "file_abc123", @@ -65,7 +65,7 @@ def test_streaming_response_get_presigned_url(self, client: Everos) -> None: assert response.http_request.headers.get("X-Stainless-Lang") == "python" object_ = response.parse() - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) assert cast(Any, response.is_closed) is True @@ -77,8 +77,8 @@ class TestAsyncObject: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_get_presigned_url(self, async_client: AsyncEveros) -> None: - object_ = await async_client.v1.object.get_presigned_url( + async def test_method_sign(self, async_client: AsyncEverOS) -> None: + object_ = await async_client.v1.object.sign( object_list=[ { "file_id": "file_abc123", @@ -87,12 +87,12 @@ async def test_method_get_presigned_url(self, async_client: AsyncEveros) -> None } ], ) - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_get_presigned_url(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.object.with_raw_response.get_presigned_url( + async def test_raw_response_sign(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.object.with_raw_response.sign( object_list=[ { "file_id": "file_abc123", @@ -105,12 +105,12 @@ async def test_raw_response_get_presigned_url(self, async_client: AsyncEveros) - assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" object_ = await response.parse() - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_get_presigned_url(self, async_client: AsyncEveros) -> None: - async with async_client.v1.object.with_streaming_response.get_presigned_url( + async def test_streaming_response_sign(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.object.with_streaming_response.sign( object_list=[ { "file_id": "file_abc123", @@ -123,6 +123,6 @@ async def test_streaming_response_get_presigned_url(self, async_client: AsyncEve assert response.http_request.headers.get("X-Stainless-Lang") == "python" object_ = await response.parse() - assert_matches_type(ObjectGetPresignedURLResponse, object_, path=["response"]) + assert_matches_type(ObjectSignResponse, object_, path=["response"]) assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/v1/test_senders.py b/tests/api_resources/v1/test_senders.py index 3303b98..f17fa02 100644 --- a/tests/api_resources/v1/test_senders.py +++ b/tests/api_resources/v1/test_senders.py @@ -7,7 +7,7 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import SenderAPIResponse @@ -19,7 +19,50 @@ class TestSenders: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_retrieve(self, client: Everos) -> None: + def test_method_create(self, client: EverOS) -> None: + sender = client.v1.senders.create( + sender_id="user_123", + ) + assert_matches_type(SenderAPIResponse, sender, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_create_with_all_params(self, client: EverOS) -> None: + sender = client.v1.senders.create( + sender_id="user_123", + name="Alice", + ) + assert_matches_type(SenderAPIResponse, sender, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_raw_response_create(self, client: EverOS) -> None: + response = client.v1.senders.with_raw_response.create( + sender_id="user_123", + ) + + assert response.is_closed is True + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + sender = response.parse() + assert_matches_type(SenderAPIResponse, sender, path=["response"]) + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_streaming_response_create(self, client: EverOS) -> None: + with client.v1.senders.with_streaming_response.create( + sender_id="user_123", + ) as response: + assert not response.is_closed + assert response.http_request.headers.get("X-Stainless-Lang") == "python" + + sender = response.parse() + assert_matches_type(SenderAPIResponse, sender, path=["response"]) + + assert cast(Any, response.is_closed) is True + + @pytest.mark.skip(reason="Mock server tests are disabled") + @parametrize + def test_method_retrieve(self, client: EverOS) -> None: sender = client.v1.senders.retrieve( "user_123", ) @@ -27,7 +70,7 @@ def test_method_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_retrieve(self, client: Everos) -> None: + def test_raw_response_retrieve(self, client: EverOS) -> None: response = client.v1.senders.with_raw_response.retrieve( "user_123", ) @@ -39,7 +82,7 @@ def test_raw_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_retrieve(self, client: Everos) -> None: + def test_streaming_response_retrieve(self, client: EverOS) -> None: with client.v1.senders.with_streaming_response.retrieve( "user_123", ) as response: @@ -53,7 +96,7 @@ def test_streaming_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_retrieve(self, client: Everos) -> None: + def test_path_params_retrieve(self, client: EverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `sender_id` but received ''"): client.v1.senders.with_raw_response.retrieve( "", @@ -61,16 +104,16 @@ def test_path_params_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update(self, client: Everos) -> None: - sender = client.v1.senders.update( + def test_method_patch(self, client: EverOS) -> None: + sender = client.v1.senders.patch( sender_id="user_123", ) assert_matches_type(SenderAPIResponse, sender, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update_with_all_params(self, client: Everos) -> None: - sender = client.v1.senders.update( + def test_method_patch_with_all_params(self, client: EverOS) -> None: + sender = client.v1.senders.patch( sender_id="user_123", name="name", ) @@ -78,8 +121,8 @@ def test_method_update_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_update(self, client: Everos) -> None: - response = client.v1.senders.with_raw_response.update( + def test_raw_response_patch(self, client: EverOS) -> None: + response = client.v1.senders.with_raw_response.patch( sender_id="user_123", ) @@ -90,8 +133,8 @@ def test_raw_response_update(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_update(self, client: Everos) -> None: - with client.v1.senders.with_streaming_response.update( + def test_streaming_response_patch(self, client: EverOS) -> None: + with client.v1.senders.with_streaming_response.patch( sender_id="user_123", ) as response: assert not response.is_closed @@ -104,24 +147,30 @@ def test_streaming_response_update(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_update(self, client: Everos) -> None: + def test_path_params_patch(self, client: EverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `sender_id` but received ''"): - client.v1.senders.with_raw_response.update( + client.v1.senders.with_raw_response.patch( sender_id="", ) + +class TestAsyncSenders: + parametrize = pytest.mark.parametrize( + "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] + ) + @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_or_update(self, client: Everos) -> None: - sender = client.v1.senders.create_or_update( + async def test_method_create(self, async_client: AsyncEverOS) -> None: + sender = await async_client.v1.senders.create( sender_id="user_123", ) assert_matches_type(SenderAPIResponse, sender, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_create_or_update_with_all_params(self, client: Everos) -> None: - sender = client.v1.senders.create_or_update( + async def test_method_create_with_all_params(self, async_client: AsyncEverOS) -> None: + sender = await async_client.v1.senders.create( sender_id="user_123", name="Alice", ) @@ -129,39 +178,33 @@ def test_method_create_or_update_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_create_or_update(self, client: Everos) -> None: - response = client.v1.senders.with_raw_response.create_or_update( + async def test_raw_response_create(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.senders.with_raw_response.create( sender_id="user_123", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - sender = response.parse() + sender = await response.parse() assert_matches_type(SenderAPIResponse, sender, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_create_or_update(self, client: Everos) -> None: - with client.v1.senders.with_streaming_response.create_or_update( + async def test_streaming_response_create(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.senders.with_streaming_response.create( sender_id="user_123", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - sender = response.parse() + sender = await response.parse() assert_matches_type(SenderAPIResponse, sender, path=["response"]) assert cast(Any, response.is_closed) is True - -class TestAsyncSenders: - parametrize = pytest.mark.parametrize( - "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] - ) - @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_retrieve(self, async_client: AsyncEveros) -> None: + async def test_method_retrieve(self, async_client: AsyncEverOS) -> None: sender = await async_client.v1.senders.retrieve( "user_123", ) @@ -169,7 +212,7 @@ async def test_method_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.senders.with_raw_response.retrieve( "user_123", ) @@ -181,7 +224,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncEverOS) -> None: async with async_client.v1.senders.with_streaming_response.retrieve( "user_123", ) as response: @@ -195,7 +238,7 @@ async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> N @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_retrieve(self, async_client: AsyncEveros) -> None: + async def test_path_params_retrieve(self, async_client: AsyncEverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `sender_id` but received ''"): await async_client.v1.senders.with_raw_response.retrieve( "", @@ -203,16 +246,16 @@ async def test_path_params_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update(self, async_client: AsyncEveros) -> None: - sender = await async_client.v1.senders.update( + async def test_method_patch(self, async_client: AsyncEverOS) -> None: + sender = await async_client.v1.senders.patch( sender_id="user_123", ) assert_matches_type(SenderAPIResponse, sender, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> None: - sender = await async_client.v1.senders.update( + async def test_method_patch_with_all_params(self, async_client: AsyncEverOS) -> None: + sender = await async_client.v1.senders.patch( sender_id="user_123", name="name", ) @@ -220,8 +263,8 @@ async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_update(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.senders.with_raw_response.update( + async def test_raw_response_patch(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.senders.with_raw_response.patch( sender_id="user_123", ) @@ -232,8 +275,8 @@ async def test_raw_response_update(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_update(self, async_client: AsyncEveros) -> None: - async with async_client.v1.senders.with_streaming_response.update( + async def test_streaming_response_patch(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.senders.with_streaming_response.patch( sender_id="user_123", ) as response: assert not response.is_closed @@ -246,51 +289,8 @@ async def test_streaming_response_update(self, async_client: AsyncEveros) -> Non @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_update(self, async_client: AsyncEveros) -> None: + async def test_path_params_patch(self, async_client: AsyncEverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `sender_id` but received ''"): - await async_client.v1.senders.with_raw_response.update( + await async_client.v1.senders.with_raw_response.patch( sender_id="", ) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_or_update(self, async_client: AsyncEveros) -> None: - sender = await async_client.v1.senders.create_or_update( - sender_id="user_123", - ) - assert_matches_type(SenderAPIResponse, sender, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_method_create_or_update_with_all_params(self, async_client: AsyncEveros) -> None: - sender = await async_client.v1.senders.create_or_update( - sender_id="user_123", - name="Alice", - ) - assert_matches_type(SenderAPIResponse, sender, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_raw_response_create_or_update(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.senders.with_raw_response.create_or_update( - sender_id="user_123", - ) - - assert response.is_closed is True - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - sender = await response.parse() - assert_matches_type(SenderAPIResponse, sender, path=["response"]) - - @pytest.mark.skip(reason="Mock server tests are disabled") - @parametrize - async def test_streaming_response_create_or_update(self, async_client: AsyncEveros) -> None: - async with async_client.v1.senders.with_streaming_response.create_or_update( - sender_id="user_123", - ) as response: - assert not response.is_closed - assert response.http_request.headers.get("X-Stainless-Lang") == "python" - - sender = await response.parse() - assert_matches_type(SenderAPIResponse, sender, path=["response"]) - - assert cast(Any, response.is_closed) is True diff --git a/tests/api_resources/v1/test_settings.py b/tests/api_resources/v1/test_settings.py index 66d7770..d5fe710 100644 --- a/tests/api_resources/v1/test_settings.py +++ b/tests/api_resources/v1/test_settings.py @@ -7,7 +7,7 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type from everos.types.v1 import SettingsAPIResponse @@ -19,13 +19,13 @@ class TestSettings: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_retrieve(self, client: Everos) -> None: + def test_method_retrieve(self, client: EverOS) -> None: setting = client.v1.settings.retrieve() assert_matches_type(SettingsAPIResponse, setting, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_retrieve(self, client: Everos) -> None: + def test_raw_response_retrieve(self, client: EverOS) -> None: response = client.v1.settings.with_raw_response.retrieve() assert response.is_closed is True @@ -35,7 +35,7 @@ def test_raw_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_retrieve(self, client: Everos) -> None: + def test_streaming_response_retrieve(self, client: EverOS) -> None: with client.v1.settings.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -47,13 +47,13 @@ def test_streaming_response_retrieve(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update(self, client: Everos) -> None: + def test_method_update(self, client: EverOS) -> None: setting = client.v1.settings.update() assert_matches_type(SettingsAPIResponse, setting, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_update_with_all_params(self, client: Everos) -> None: + def test_method_update_with_all_params(self, client: EverOS) -> None: setting = client.v1.settings.update( boundary_detection_timeout=3600, extraction_mode="default", @@ -77,7 +77,7 @@ def test_method_update_with_all_params(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_update(self, client: Everos) -> None: + def test_raw_response_update(self, client: EverOS) -> None: response = client.v1.settings.with_raw_response.update() assert response.is_closed is True @@ -87,7 +87,7 @@ def test_raw_response_update(self, client: Everos) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_update(self, client: Everos) -> None: + def test_streaming_response_update(self, client: EverOS) -> None: with client.v1.settings.with_streaming_response.update() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -105,13 +105,13 @@ class TestAsyncSettings: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_retrieve(self, async_client: AsyncEveros) -> None: + async def test_method_retrieve(self, async_client: AsyncEverOS) -> None: setting = await async_client.v1.settings.retrieve() assert_matches_type(SettingsAPIResponse, setting, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_raw_response_retrieve(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.settings.with_raw_response.retrieve() assert response.is_closed is True @@ -121,7 +121,7 @@ async def test_raw_response_retrieve(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_retrieve(self, async_client: AsyncEverOS) -> None: async with async_client.v1.settings.with_streaming_response.retrieve() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" @@ -133,13 +133,13 @@ async def test_streaming_response_retrieve(self, async_client: AsyncEveros) -> N @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update(self, async_client: AsyncEveros) -> None: + async def test_method_update(self, async_client: AsyncEverOS) -> None: setting = await async_client.v1.settings.update() assert_matches_type(SettingsAPIResponse, setting, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> None: + async def test_method_update_with_all_params(self, async_client: AsyncEverOS) -> None: setting = await async_client.v1.settings.update( boundary_detection_timeout=3600, extraction_mode="default", @@ -163,7 +163,7 @@ async def test_method_update_with_all_params(self, async_client: AsyncEveros) -> @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_update(self, async_client: AsyncEveros) -> None: + async def test_raw_response_update(self, async_client: AsyncEverOS) -> None: response = await async_client.v1.settings.with_raw_response.update() assert response.is_closed is True @@ -173,7 +173,7 @@ async def test_raw_response_update(self, async_client: AsyncEveros) -> None: @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_update(self, async_client: AsyncEveros) -> None: + async def test_streaming_response_update(self, async_client: AsyncEverOS) -> None: async with async_client.v1.settings.with_streaming_response.update() as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" diff --git a/tests/api_resources/test_v1.py b/tests/api_resources/v1/test_tasks.py similarity index 54% rename from tests/api_resources/test_v1.py rename to tests/api_resources/v1/test_tasks.py index 57747f7..85f1217 100644 --- a/tests/api_resources/test_v1.py +++ b/tests/api_resources/v1/test_tasks.py @@ -7,102 +7,102 @@ import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from tests.utils import assert_matches_type -from everos.types import V1QueryTaskStatusResponse +from everos.types.v1 import GetTaskStatusResponse base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010") -class TestV1: +class TestTasks: parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_method_query_task_status(self, client: Everos) -> None: - v1 = client.v1.query_task_status( + def test_method_retrieve(self, client: EverOS) -> None: + task = client.v1.tasks.retrieve( "task_id", ) - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_raw_response_query_task_status(self, client: Everos) -> None: - response = client.v1.with_raw_response.query_task_status( + def test_raw_response_retrieve(self, client: EverOS) -> None: + response = client.v1.tasks.with_raw_response.retrieve( "task_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - v1 = response.parse() - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + task = response.parse() + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_streaming_response_query_task_status(self, client: Everos) -> None: - with client.v1.with_streaming_response.query_task_status( + def test_streaming_response_retrieve(self, client: EverOS) -> None: + with client.v1.tasks.with_streaming_response.retrieve( "task_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - v1 = response.parse() - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + task = response.parse() + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - def test_path_params_query_task_status(self, client: Everos) -> None: + def test_path_params_retrieve(self, client: EverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): - client.v1.with_raw_response.query_task_status( + client.v1.tasks.with_raw_response.retrieve( "", ) -class TestAsyncV1: +class TestAsyncTasks: parametrize = pytest.mark.parametrize( "async_client", [False, True, {"http_client": "aiohttp"}], indirect=True, ids=["loose", "strict", "aiohttp"] ) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_method_query_task_status(self, async_client: AsyncEveros) -> None: - v1 = await async_client.v1.query_task_status( + async def test_method_retrieve(self, async_client: AsyncEverOS) -> None: + task = await async_client.v1.tasks.retrieve( "task_id", ) - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_raw_response_query_task_status(self, async_client: AsyncEveros) -> None: - response = await async_client.v1.with_raw_response.query_task_status( + async def test_raw_response_retrieve(self, async_client: AsyncEverOS) -> None: + response = await async_client.v1.tasks.with_raw_response.retrieve( "task_id", ) assert response.is_closed is True assert response.http_request.headers.get("X-Stainless-Lang") == "python" - v1 = await response.parse() - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + task = await response.parse() + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_streaming_response_query_task_status(self, async_client: AsyncEveros) -> None: - async with async_client.v1.with_streaming_response.query_task_status( + async def test_streaming_response_retrieve(self, async_client: AsyncEverOS) -> None: + async with async_client.v1.tasks.with_streaming_response.retrieve( "task_id", ) as response: assert not response.is_closed assert response.http_request.headers.get("X-Stainless-Lang") == "python" - v1 = await response.parse() - assert_matches_type(V1QueryTaskStatusResponse, v1, path=["response"]) + task = await response.parse() + assert_matches_type(GetTaskStatusResponse, task, path=["response"]) assert cast(Any, response.is_closed) is True @pytest.mark.skip(reason="Mock server tests are disabled") @parametrize - async def test_path_params_query_task_status(self, async_client: AsyncEveros) -> None: + async def test_path_params_retrieve(self, async_client: AsyncEverOS) -> None: with pytest.raises(ValueError, match=r"Expected a non-empty value for `task_id` but received ''"): - await async_client.v1.with_raw_response.query_task_status( + await async_client.v1.tasks.with_raw_response.retrieve( "", ) diff --git a/tests/conftest.py b/tests/conftest.py index 1ff0005..b50badd 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -10,7 +10,7 @@ import pytest from pytest_asyncio import is_async_test -from everos import Everos, AsyncEveros, DefaultAioHttpClient +from everos import EverOS, AsyncEverOS, DefaultAioHttpClient from everos._utils import is_dict if TYPE_CHECKING: @@ -49,17 +49,17 @@ def pytest_collection_modifyitems(items: list[pytest.Function]) -> None: @pytest.fixture(scope="session") -def client(request: FixtureRequest) -> Iterator[Everos]: +def client(request: FixtureRequest) -> Iterator[EverOS]: strict = getattr(request, "param", True) if not isinstance(strict, bool): raise TypeError(f"Unexpected fixture parameter type {type(strict)}, expected {bool}") - with Everos(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: + with EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=strict) as client: yield client @pytest.fixture(scope="session") -async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncEveros]: +async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncEverOS]: param = getattr(request, "param", True) # defaults @@ -78,7 +78,7 @@ async def async_client(request: FixtureRequest) -> AsyncIterator[AsyncEveros]: else: raise TypeError(f"Unexpected fixture parameter type {type(param)}, expected bool or dict") - async with AsyncEveros( + async with AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=strict, http_client=http_client ) as client: yield client diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/lib/conftest.py b/tests/lib/conftest.py new file mode 100644 index 0000000..6202e41 --- /dev/null +++ b/tests/lib/conftest.py @@ -0,0 +1,9 @@ +"""Add src/ to sys.path so EverOS package is importable in all lib tests.""" +from __future__ import annotations + +import sys +from pathlib import Path + +src = str(Path(__file__).parent.parent.parent / "src") +if src not in sys.path: + sys.path.insert(0, src) diff --git a/tests/lib/test_detect.py b/tests/lib/test_detect.py new file mode 100644 index 0000000..6b5bfee --- /dev/null +++ b/tests/lib/test_detect.py @@ -0,0 +1,197 @@ +"""Unit tests for everos.lib._detect — URI detection and message scanning.""" +from __future__ import annotations + +from typing import Any +from pathlib import Path + +import pytest + +from everos.lib._detect import ( + _is_http_uri, + scan_messages, + _is_local_file, +) + +# ── _is_http_uri ───────────────────────────────────────────────────────────── + + +def test_is_http_uri_http() -> None: + assert _is_http_uri("http://example.com/file.jpg") is True + + +def test_is_http_uri_https() -> None: + assert _is_http_uri("https://example.com/image.png") is True + + +def test_is_http_uri_local_path() -> None: + assert _is_http_uri("./photo.jpg") is False + + +def test_is_http_uri_object_key() -> None: + assert _is_http_uri("uploads/2026/abc/photo.jpg") is False + + +def test_is_http_uri_ftp() -> None: + assert _is_http_uri("ftp://example.com/file") is False + + +# ── _is_local_file ──────────────────────────────────────────────────────────── + + +def test_is_local_file_existing(tmp_path: Path) -> None: + f = tmp_path / "photo.jpg" + f.write_bytes(b"data") + assert _is_local_file(str(f)) is True + + +def test_is_local_file_nonexistent() -> None: + assert _is_local_file("/nonexistent/path/file.jpg") is False + + +def test_is_local_file_directory(tmp_path: Path) -> None: + assert _is_local_file(str(tmp_path)) is False + + +def test_is_local_file_protocol_prefix() -> None: + # Anything with "://" in it should not be treated as a local file + assert _is_local_file("s3://bucket/key") is False + assert _is_local_file("minio://bucket/key.jpg") is False + + +def test_is_local_file_tilde_expansion(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + f = tmp_path / "notes.txt" + f.write_bytes(b"hello") + monkeypatch.setenv("HOME", str(tmp_path)) + assert _is_local_file("~/notes.txt") is True + + +def test_is_local_file_object_key_not_local() -> None: + # A MinIO object_key like "uploads/uuid/file.jpg" won't exist on disk + assert _is_local_file("uploads/abc123-uuid/photo.png") is False + + +# ── scan_messages ───────────────────────────────────────────────────────────── + + +def test_scan_messages_empty() -> None: + assert scan_messages([]) == [] + + +def test_scan_messages_text_only() -> None: + msgs = [{"role": "user", "content": [{"type": "text", "text": "hello"}]}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_str_content_skipped() -> None: + # str shorthand for content → pure text, skip + msgs = [{"role": "user", "content": "plain text message"}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_none_content_skipped() -> None: + msgs = [{"role": "user"}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_empty_uri_skipped() -> None: + msgs = [{"role": "user", "content": [{"type": "image", "uri": ""}]}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_none_uri_skipped() -> None: + msgs = [{"role": "user", "content": [{"type": "image"}]}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_object_key_passthrough() -> None: + # Already an object_key — not a local file, not http → passthrough + msgs = [{"role": "user", "content": [{"type": "image", "uri": "uploads/uuid/photo.jpg"}]}] + assert scan_messages(msgs) == [] + + +def test_scan_messages_http_url(tmp_path: Path) -> None: # noqa: ARG001 + msgs = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "see image"}, + {"type": "image", "uri": "https://example.com/photo.jpg", "name": "photo.jpg"}, + ], + } + ] + tasks = scan_messages(msgs) + assert len(tasks) == 1 + t = tasks[0] + assert t.msg_idx == 0 + assert t.content_idx == 1 + assert t.uri == "https://example.com/photo.jpg" + assert t.uri_type == "http" + assert t.content_type == "image" + assert t.file_type == "image" + assert t.file_name == "photo.jpg" + + +def test_scan_messages_local_file(tmp_path: Path) -> None: + f = tmp_path / "doc.pdf" + f.write_bytes(b"pdf") + msgs = [{"role": "user", "content": [{"type": "pdf", "uri": str(f), "ext": "pdf"}]}] + tasks = scan_messages(msgs) + assert len(tasks) == 1 + t = tasks[0] + assert t.uri_type == "local" + assert t.content_type == "pdf" + assert t.file_type == "file" + assert t.file_ext == "pdf" + + +def test_scan_messages_multiple_messages_and_items(tmp_path: Path) -> None: + f = tmp_path / "audio.mp3" + f.write_bytes(b"mp3") + + msgs = [ + { + "role": "user", + "content": [ + {"type": "text", "text": "hello"}, + {"type": "audio", "uri": str(f)}, + ], + }, + { + "role": "assistant", + "content": [ + {"type": "image", "uri": "https://example.com/img.png"}, + {"type": "doc", "uri": "uploads/existing-key/file.docx"}, # passthrough + ], + }, + ] + tasks = scan_messages(msgs) + assert len(tasks) == 2 + + audio_task = tasks[0] + assert audio_task.msg_idx == 0 + assert audio_task.content_idx == 1 + assert audio_task.uri_type == "local" + assert audio_task.file_type == "file" + + img_task = tasks[1] + assert img_task.msg_idx == 1 + assert img_task.content_idx == 0 + assert img_task.uri_type == "http" + assert img_task.file_type == "image" + + +def test_scan_messages_content_type_mapping() -> None: + """All non-image content types map to file_type='file'.""" + for ct in ("audio", "doc", "pdf", "html", "email"): + msgs = [{"role": "user", "content": [{"type": ct, "uri": "https://example.com/x"}]}] + tasks = scan_messages(msgs) + assert len(tasks) == 1 + assert tasks[0].file_type == "file", f"Expected file_type='file' for type={ct}" + + +def test_scan_messages_does_not_mutate() -> None: + """scan_messages must not mutate the input messages.""" + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": "https://example.com/x.jpg"}]}] + original_uri = msgs[0]["content"][0]["uri"] + scan_messages(msgs) + assert msgs[0]["content"][0]["uri"] == original_uri diff --git a/tests/lib/test_files.py b/tests/lib/test_files.py new file mode 100644 index 0000000..c076ec0 --- /dev/null +++ b/tests/lib/test_files.py @@ -0,0 +1,278 @@ +"""Unit tests for everos.lib._files — FileInput, ResolvedFile, resolve_file.""" +from __future__ import annotations + +import os +import tempfile +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from everos.lib._files import ( + FileInput, + ResolvedFile, + resolve_file, + async_resolve_file, +) +from everos.lib._errors import FileResolveError + +# ── FileInput validation ────────────────────────────────────────────────────── + + +def test_file_input_path_only(tmp_path: Path) -> None: + fi = FileInput(path=str(tmp_path / "file.txt")) + assert fi.path is not None + assert fi.url is None + + +def test_file_input_url_only() -> None: + fi = FileInput(url="https://example.com/file.jpg") + assert fi.url is not None + assert fi.path is None + + +def test_file_input_both_raises() -> None: + with pytest.raises(ValueError, match="only one"): + FileInput(path="./file.jpg", url="https://example.com/file.jpg") + + +def test_file_input_neither_raises() -> None: + with pytest.raises(ValueError, match="requires exactly one"): + FileInput() + + +# ── ResolvedFile ────────────────────────────────────────────────────────────── + + +def test_resolved_file_cleanup_temp(tmp_path: Path) -> None: + f = tmp_path / "temp.bin" + f.write_bytes(b"data") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="temp.bin", size=4, is_temp=True) + assert f.exists() + rf.cleanup() + assert not f.exists() + + +def test_resolved_file_cleanup_non_temp(tmp_path: Path) -> None: + f = tmp_path / "orig.bin" + f.write_bytes(b"data") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="orig.bin", size=4, is_temp=False) + rf.cleanup() + assert f.exists() # should NOT be deleted + + +def test_resolved_file_cleanup_idempotent(tmp_path: Path) -> None: + f = tmp_path / "temp2.bin" + f.write_bytes(b"x") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="temp2.bin", size=1, is_temp=True) + rf.cleanup() + rf.cleanup() # second call must not raise + + +def test_resolved_file_open(tmp_path: Path) -> None: + f = tmp_path / "data.bin" + f.write_bytes(b"hello world") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="data.bin", size=11, is_temp=False) + with rf.open() as fh: + assert fh.read() == b"hello world" + + +# ── resolve_file (local path) ───────────────────────────────────────────────── + + +def test_resolve_file_local_existing(tmp_path: Path) -> None: + f = tmp_path / "photo.jpg" + f.write_bytes(b"\xff\xd8\xff" * 10) + fi = FileInput(path=str(f)) + rf = resolve_file(fi) + assert rf.filename == "photo.jpg" + assert rf.size == 30 + assert rf.is_temp is False + assert rf.file_path == f + + +def test_resolve_file_local_missing() -> None: + fi = FileInput(path="/nonexistent/totally/missing.jpg") + with pytest.raises(FileResolveError, match="not found"): + resolve_file(fi) + + +def test_resolve_file_local_directory(tmp_path: Path) -> None: + fi = FileInput(path=str(tmp_path)) + with pytest.raises(FileResolveError, match="Not a file"): + resolve_file(fi) + + +def test_resolve_file_local_mime_inference(tmp_path: Path) -> None: + for fname, expected_mime in [("photo.jpg", "image/jpeg"), ("doc.pdf", "application/pdf")]: + f = tmp_path / fname + f.write_bytes(b"data") + rf = resolve_file(FileInput(path=str(f))) + assert rf.content_type == expected_mime, f"Expected {expected_mime} for {fname}" + + +def test_resolve_file_local_explicit_content_type(tmp_path: Path) -> None: + f = tmp_path / "data.bin" + f.write_bytes(b"data") + rf = resolve_file(FileInput(path=str(f), content_type="image/webp")) + assert rf.content_type == "image/webp" + + +def test_resolve_file_local_explicit_filename(tmp_path: Path) -> None: + f = tmp_path / "data.bin" + f.write_bytes(b"data") + rf = resolve_file(FileInput(path=str(f), filename="custom_name.bin")) + assert rf.filename == "custom_name.bin" + + +def test_resolve_file_local_tilde(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + f = tmp_path / "me.txt" + f.write_bytes(b"hi") + monkeypatch.setenv("HOME", str(tmp_path)) + rf = resolve_file(FileInput(path="~/me.txt")) + assert rf.size == 2 + + +# ── resolve_file (URL download) ─────────────────────────────────────────────── + + +def _make_mock_response(content: bytes, content_type: str = "image/jpeg", status: int = 200) -> MagicMock: + """Build a mock httpx streaming response.""" + mock_resp = MagicMock() + mock_resp.status_code = status + mock_resp.headers = {"content-type": content_type} + + def raise_for_status() -> None: + if status >= 400: + raise Exception(f"HTTP {status}") + + mock_resp.raise_for_status = raise_for_status + mock_resp.iter_bytes = lambda chunk_size=None: iter([content]) # noqa: ARG005 # pyright: ignore[reportUnknownLambdaType] + mock_resp.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_resp.__exit__ = MagicMock(return_value=False) + return mock_resp + + +def test_resolve_file_url_downloads_to_temp() -> None: + content = b"fake-image-data" + fi = FileInput(url="https://example.com/photo.jpg") + + mock_client = MagicMock() + mock_client.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_client.__exit__ = MagicMock(return_value=False) + mock_client.stream = MagicMock(return_value=_make_mock_response(content, "image/jpeg")) + + with patch("everos.lib._files.httpx.Client", return_value=mock_client): + rf = resolve_file(fi) + + assert rf.is_temp is True + assert rf.size == len(content) + assert rf.content_type == "image/jpeg" + assert rf.filename == "photo.jpg" + rf.cleanup() + + +def test_resolve_file_url_filename_fallback() -> None: + """URLs with no path segment fall back to 'download'.""" + content = b"data" + fi = FileInput(url="https://example.com/") + + mock_client = MagicMock() + mock_client.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_client.__exit__ = MagicMock(return_value=False) + mock_client.stream = MagicMock(return_value=_make_mock_response(content)) + + with patch("everos.lib._files.httpx.Client", return_value=mock_client): + rf = resolve_file(fi) + + assert rf.filename == "download" + rf.cleanup() + + +def test_resolve_file_url_size_limit_exceeded() -> None: + big_chunk = b"x" * 1024 # 1 KB + fi = FileInput(url="https://example.com/big.bin") + + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.headers = {"content-type": "application/octet-stream"} + mock_resp.raise_for_status = MagicMock() + mock_resp.iter_bytes = lambda chunk_size=None: iter([big_chunk, big_chunk]) # noqa: ARG005 # pyright: ignore[reportUnknownLambdaType] # 2 KB total + mock_resp.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_resp.__exit__ = MagicMock(return_value=False) + + mock_client = MagicMock() + mock_client.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_client.__exit__ = MagicMock(return_value=False) + mock_client.stream = MagicMock(return_value=mock_resp) + + with patch("everos.lib._files.httpx.Client", return_value=mock_client): + with pytest.raises(FileResolveError, match="exceeded"): + resolve_file(fi, max_download_size=1500) # limit < 2 KB + + +def test_resolve_file_url_cleans_up_temp_on_error() -> None: + fi = FileInput(url="https://example.com/fail.jpg") + + mock_client = MagicMock() + mock_client.__enter__ = lambda s: s # pyright: ignore[reportUnknownLambdaType] + mock_client.__exit__ = MagicMock(return_value=False) + mock_client.stream = MagicMock(side_effect=Exception("connection refused")) + + created_temps: list[str] = [] + original_mkstemp = tempfile.mkstemp + + def spy_mkstemp(**_kwargs: object) -> tuple[int, str]: + fd, path = original_mkstemp(prefix="everos_") + created_temps.append(path) + return fd, path + + with patch("everos.lib._files.httpx.Client", return_value=mock_client): + with patch("everos.lib._files.tempfile.mkstemp", side_effect=spy_mkstemp): + with pytest.raises(FileResolveError): + resolve_file(fi) + + for p in created_temps: + assert not os.path.exists(p), f"Temp file {p} was not cleaned up" + + +# ── async_resolve_file ──────────────────────────────────────────────────────── + + +async def test_async_resolve_file_local(tmp_path: Path) -> None: + f = tmp_path / "audio.mp3" + f.write_bytes(b"\x00" * 100) + rf = await async_resolve_file(FileInput(path=str(f))) + assert rf.filename == "audio.mp3" + assert rf.size == 100 + assert rf.is_temp is False + + +async def test_async_resolve_file_url() -> None: + content = b"async-image-data" + fi = FileInput(url="https://example.com/async.jpg") + + # Use MagicMock (not AsyncMock) for the response so headers remains a plain dict + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_resp.headers = {"content-type": "image/jpeg"} + mock_resp.raise_for_status = MagicMock() + + async def aiter_bytes(chunk_size: int = 65536) -> None: # noqa: ARG001 # type: ignore[override] + yield content # pyright: ignore[reportReturnType] + + mock_resp.aiter_bytes = aiter_bytes + mock_resp.__aenter__ = AsyncMock(return_value=mock_resp) + mock_resp.__aexit__ = AsyncMock(return_value=False) + + mock_client = MagicMock() + mock_client.__aenter__ = AsyncMock(return_value=mock_client) + mock_client.__aexit__ = AsyncMock(return_value=False) + mock_client.stream = MagicMock(return_value=mock_resp) + + with patch("everos.lib._files.httpx.AsyncClient", return_value=mock_client): + rf = await async_resolve_file(fi) + + assert rf.is_temp is True + assert rf.size == len(content) + rf.cleanup() diff --git a/tests/lib/test_multimodal.py b/tests/lib/test_multimodal.py new file mode 100644 index 0000000..90363a1 --- /dev/null +++ b/tests/lib/test_multimodal.py @@ -0,0 +1,350 @@ +"""Unit tests for everos.lib._multimodal — orchestration and resource override.""" +from __future__ import annotations + +from typing import Any +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, patch + +import pytest + +from everos.lib._files import ResolvedFile +from everos.lib._detect import UploadTask +from everos.lib._errors import UploadError, MultimodalError +from everos.lib._upload import UploadResult +from everos.lib._multimodal import ( + MemoriesResourceWithMultimodal, + AsyncMemoriesResourceWithMultimodal, + _cleanup, + _replace_uris, +) + +# ── Helpers ─────────────────────────────────────────────────────────────────── + + +def _make_upload_result(object_key: str = "uploads/uuid/photo.jpg", filename: str = "photo.jpg") -> UploadResult: + return UploadResult(object_key=object_key, filename=filename, content_type="image/jpeg", size=10) + + +def _make_task(msg_idx: int = 0, content_idx: int = 1, uri: str = "photo.jpg") -> UploadTask: + return UploadTask( + msg_idx=msg_idx, content_idx=content_idx, uri=uri, + uri_type="local", content_type="image", file_type="image", + file_name="photo.jpg", file_ext="jpg", + ) + + +def _make_add_response() -> MagicMock: + resp = MagicMock() + resp.status = 0 + return resp + + +# ── _replace_uris ───────────────────────────────────────────────────────────── + + +def test_replace_uris_basic() -> None: + msg_list: list[dict[str, Any]] = [{"role": "user", "content": [ + {"type": "text", "text": "hello"}, + {"type": "image", "uri": "./photo.jpg"}, + ]}] + tasks = [_make_task(0, 1, "./photo.jpg")] + results = [_make_upload_result("uploads/uuid/photo.jpg", "photo.jpg")] + + _replace_uris(msg_list, tasks, results) + + item = msg_list[0]["content"][1] + assert item["uri"] == "uploads/uuid/photo.jpg" + + +def test_replace_uris_auto_fills_name() -> None: + msg_list: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": "./photo.jpg"}]}] + tasks = [_make_task(0, 0)] + results = [_make_upload_result(filename="photo.jpg")] + + _replace_uris(msg_list, tasks, results) + assert msg_list[0]["content"][0]["name"] == "photo.jpg" + + +def test_replace_uris_auto_fills_ext() -> None: + msg_list: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": "./photo.jpg"}]}] + tasks = [_make_task(0, 0)] + results = [_make_upload_result(filename="photo.jpg")] + + _replace_uris(msg_list, tasks, results) + assert msg_list[0]["content"][0]["ext"] == "jpg" + + +def test_replace_uris_does_not_overwrite_existing_name() -> None: + msg_list: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": "./x.jpg", "name": "custom_name.jpg"}]}] + tasks = [_make_task(0, 0, "./x.jpg")] + results = [_make_upload_result(filename="x.jpg")] + + _replace_uris(msg_list, tasks, results) + assert msg_list[0]["content"][0]["name"] == "custom_name.jpg" + + +def test_replace_uris_multiple_items() -> None: + msg_list: list[dict[str, Any]] = [{"role": "user", "content": [ + {"type": "image", "uri": "./a.jpg"}, + {"type": "doc", "uri": "./b.pdf"}, + ]}] + tasks = [_make_task(0, 0, "./a.jpg"), _make_task(0, 1, "./b.pdf")] + results = [ + _make_upload_result("uploads/uuid/a.jpg", "a.jpg"), + _make_upload_result("uploads/uuid/b.pdf", "b.pdf"), + ] + + _replace_uris(msg_list, tasks, results) + assert msg_list[0]["content"][0]["uri"] == "uploads/uuid/a.jpg" + assert msg_list[0]["content"][1]["uri"] == "uploads/uuid/b.pdf" + + +# ── _cleanup ────────────────────────────────────────────────────────────────── + + +def test_cleanup_removes_temp_files(tmp_path: Path) -> None: + f = tmp_path / "tmp.bin" + f.write_bytes(b"x") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="tmp.bin", size=1, is_temp=True) + _cleanup([rf]) + assert not f.exists() + + +def test_cleanup_skips_non_temp(tmp_path: Path) -> None: + f = tmp_path / "orig.bin" + f.write_bytes(b"x") + rf = ResolvedFile(file_path=f, content_type="application/octet-stream", filename="orig.bin", size=1, is_temp=False) + _cleanup([rf]) + assert f.exists() + + +# ── MemoriesResourceWithMultimodal (sync) ───────────────────────────────────── + + +def _make_sync_resource(_tmp_path: Path) -> tuple[MemoriesResourceWithMultimodal, MagicMock]: + """Return a resource instance and the mock super().add response.""" + mock_client = MagicMock() + resource = MemoriesResourceWithMultimodal.__new__(MemoriesResourceWithMultimodal) + resource._client = mock_client + return resource, mock_client + + +def test_sync_add_passthrough_text_only(tmp_path: Path) -> None: + resource, _ = _make_sync_resource(tmp_path) + add_response = _make_add_response() + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "text", "text": "hello"}]}] + + with patch.object( + MemoriesResourceWithMultimodal.__bases__[0], "add", return_value=add_response + ) as mock_super: + result = resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + mock_super.assert_called_once() + assert result is add_response + + +def test_sync_add_local_file_upload(tmp_path: Path) -> None: + resource, mock_client = _make_sync_resource(tmp_path) + + f = tmp_path / "photo.jpg" + f.write_bytes(b"fake-image" * 10) + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [ + {"type": "text", "text": "see this"}, + {"type": "image", "uri": str(f), "name": "photo.jpg", "ext": "jpg"}, + ]}] + + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + signed_item = MagicMock() + signed_item.object_key = "uploads/uuid/photo.jpg" + signed_item.object_signed_info.url = "https://s3.example.com/" + signed_item.object_signed_info.fields = {"policy": "x"} + sign_resp.result.data.object_list = [signed_item] + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + add_response = _make_add_response() + captured_msgs: list[Any] = [] + + def _super_add(**kwargs: Any) -> MagicMock: + captured_msgs.extend(kwargs["messages"]) + return add_response + + with patch("everos.lib._upload.httpx.post") as mock_s3: + mock_s3.return_value = MagicMock(status_code=204) + with patch.object( + MemoriesResourceWithMultimodal.__bases__[0], "add", side_effect=_super_add + ): + result = resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + assert result is add_response + # URI should have been replaced in the message passed to super().add() + assert captured_msgs[0]["content"][1]["uri"] == "uploads/uuid/photo.jpg" + # Original msgs must NOT be mutated + assert msgs[0]["content"][1]["uri"] == str(f) + + +def test_sync_add_deepcopy_original_not_mutated(tmp_path: Path) -> None: + resource, mock_client = _make_sync_resource(tmp_path) + f = tmp_path / "img.jpg" + f.write_bytes(b"data") + original_uri = str(f) + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": original_uri}]}] + + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + item = MagicMock() + item.object_key = "uploads/uuid/img.jpg" + item.object_signed_info.url = "https://s3.example.com/" + item.object_signed_info.fields = {} + sign_resp.result.data.object_list = [item] + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + with patch("everos.lib._upload.httpx.post", return_value=MagicMock(status_code=204)): + with patch.object(MemoriesResourceWithMultimodal.__bases__[0], "add", return_value=_make_add_response()): + resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + assert msgs[0]["content"][0]["uri"] == original_uri + + +def test_sync_add_cleanup_on_success(tmp_path: Path) -> None: + resource, mock_client = _make_sync_resource(tmp_path) + f = tmp_path / "photo.jpg" + f.write_bytes(b"data") + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": str(f)}]}] + + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + item = MagicMock() + item.object_key = "uploads/uuid/photo.jpg" + item.object_signed_info.url = "https://s3.example.com/" + item.object_signed_info.fields = {} + sign_resp.result.data.object_list = [item] + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + with patch("everos.lib._multimodal._cleanup") as mock_cleanup: + with patch("everos.lib._upload.httpx.post", return_value=MagicMock(status_code=204)): + with patch.object(MemoriesResourceWithMultimodal.__bases__[0], "add", return_value=_make_add_response()): + resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + mock_cleanup.assert_called_once() + + +def test_sync_add_cleanup_on_upload_failure(tmp_path: Path) -> None: + resource, mock_client = _make_sync_resource(tmp_path) + f = tmp_path / "photo.jpg" + f.write_bytes(b"data") + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "image", "uri": str(f)}]}] + + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + item = MagicMock() + item.object_key = "uploads/uuid/photo.jpg" + item.object_signed_info.url = "https://s3.example.com/" + item.object_signed_info.fields = {} + sign_resp.result.data.object_list = [item] + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + with patch("everos.lib._multimodal._cleanup") as mock_cleanup: + with patch("everos.lib._upload.httpx.post", return_value=MagicMock(status_code=500)): + with patch("everos.lib._upload.time.sleep"): + with pytest.raises(MultimodalError): + resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + mock_cleanup.assert_called_once() + + +# ── AsyncMemoriesResourceWithMultimodal ─────────────────────────────────────── + + +def _make_async_resource(_tmp_path: Path) -> AsyncMemoriesResourceWithMultimodal: + mock_client = MagicMock() + resource = AsyncMemoriesResourceWithMultimodal.__new__(AsyncMemoriesResourceWithMultimodal) + resource._client = mock_client + return resource + + +async def test_async_add_passthrough_text_only() -> None: + resource = AsyncMemoriesResourceWithMultimodal.__new__(AsyncMemoriesResourceWithMultimodal) + resource._client = MagicMock() + add_response = _make_add_response() + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "text", "text": "hi"}]}] + + with patch.object( + AsyncMemoriesResourceWithMultimodal.__bases__[0], "add", + new_callable=AsyncMock, return_value=add_response, + ) as mock_super: + result = await resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + mock_super.assert_called_once() + assert result is add_response + + +async def test_async_add_local_file_upload(tmp_path: Path) -> None: + resource = _make_async_resource(tmp_path) + f = tmp_path / "audio.mp3" + f.write_bytes(b"mp3" * 20) + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "audio", "uri": str(f)}]}] + + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + item = MagicMock() + item.object_key = "uploads/uuid/audio.mp3" + item.object_signed_info.url = "https://s3.example.com/" + item.object_signed_info.fields = {"policy": "y"} + sign_resp.result.data.object_list = [item] + resource._client.v1.object.sign = AsyncMock(return_value=sign_resp) + + captured_msgs: list[Any] = [] + + async def _super_add(**kwargs: Any) -> MagicMock: + captured_msgs.extend(kwargs["messages"]) + return _make_add_response() + + mock_resp = MagicMock() + mock_resp.status_code = 200 + mock_http = AsyncMock() + mock_http.post = AsyncMock(return_value=mock_resp) + mock_http.__aenter__ = AsyncMock(return_value=mock_http) + mock_http.__aexit__ = AsyncMock(return_value=False) + + with patch("everos.lib._upload.httpx.AsyncClient", return_value=mock_http): + with patch.object( + AsyncMemoriesResourceWithMultimodal.__bases__[0], "add", + side_effect=_super_add, + ): + await resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + assert captured_msgs[0]["content"][0]["uri"] == "uploads/uuid/audio.mp3" + # Original untouched + assert msgs[0]["content"][0]["uri"] == str(f) + + +async def test_async_add_cleanup_called_on_failure(tmp_path: Path) -> None: + resource = _make_async_resource(tmp_path) + f = tmp_path / "doc.pdf" + f.write_bytes(b"pdf") + + msgs: list[dict[str, Any]] = [{"role": "user", "content": [{"type": "pdf", "uri": str(f)}]}] + + sign_resp = MagicMock() + sign_resp.status = 1 # sign failure + sign_resp.error = "InternalError" + resource._client.v1.object.sign = AsyncMock(return_value=sign_resp) + + with patch("everos.lib._multimodal._cleanup") as mock_cleanup: + with pytest.raises((UploadError, MultimodalError)): + await resource.add(messages=msgs, user_id="u1") # type: ignore[arg-type] + + mock_cleanup.assert_called_once() diff --git a/tests/lib/test_upload.py b/tests/lib/test_upload.py new file mode 100644 index 0000000..61f9995 --- /dev/null +++ b/tests/lib/test_upload.py @@ -0,0 +1,333 @@ +"""Unit tests for everos.lib._upload — batch_sign and s3_post_upload.""" +from __future__ import annotations + +from typing import Any +from pathlib import Path +from unittest.mock import AsyncMock, MagicMock, call, patch + +import httpx +import pytest + +from everos.lib._files import ResolvedFile +from everos.lib._detect import UploadTask +from everos.lib._errors import UploadError +from everos.lib._upload import ( + _RETRY_BACKOFF_S, + _MAX_UPLOAD_RETRIES, + SignedFile, + batch_sign, + s3_post_upload, + async_batch_sign, + async_s3_post_upload, +) + +# ── Helpers ─────────────────────────────────────────────────────────────────── + + +def _make_resolved(tmp_path: Path, name: str = "photo.jpg", size: int = 10) -> ResolvedFile: + f = tmp_path / name + f.write_bytes(b"x" * size) + return ResolvedFile(file_path=f, content_type="image/jpeg", filename=name, size=size, is_temp=False) + + +def _make_task(msg_idx: int = 0, content_idx: int = 0, uri: str = "photo.jpg") -> UploadTask: + return UploadTask( + msg_idx=msg_idx, + content_idx=content_idx, + uri=uri, + uri_type="local", + content_type="image", + file_type="image", + file_name="photo.jpg", + file_ext="jpg", + ) + + +def _make_sign_response(items: list[dict[str, Any]]) -> MagicMock: + sign_resp = MagicMock() + sign_resp.status = 0 + sign_resp.error = "OK" + + data = MagicMock() + object_items: list[MagicMock] = [] + for it in items: + item = MagicMock() + item.object_key = it["object_key"] + item.object_signed_info.url = it["url"] + item.object_signed_info.fields = it.get("fields", {}) + object_items.append(item) + + data.object_list = object_items + result = MagicMock() + result.data = data + sign_resp.result = result + return sign_resp + + +def _make_signed_file(tmp_path: Path, name: str = "photo.jpg") -> SignedFile: + return SignedFile( + resolved=_make_resolved(tmp_path, name), + task=_make_task(), + object_key=f"uploads/uuid/{name}", + upload_url="https://s3.example.com/bucket", + upload_fields={"key": f"uploads/uuid/{name}", "policy": "abc"}, + ) + + +# ── batch_sign ──────────────────────────────────────────────────────────────── + + +def test_batch_sign_success(tmp_path: Path) -> None: + resolved = [_make_resolved(tmp_path)] + tasks = [_make_task()] + + sign_resp = _make_sign_response([ + {"object_key": "uploads/uuid/photo.jpg", "url": "https://s3.example.com/", "fields": {"policy": "x"}}, + ]) + + mock_client = MagicMock() + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + signed = batch_sign(mock_client, tasks, resolved) + + assert len(signed) == 1 + assert signed[0].object_key == "uploads/uuid/photo.jpg" + assert signed[0].upload_url == "https://s3.example.com/" + assert signed[0].upload_fields == {"policy": "x"} + + +def test_batch_sign_passes_file_id_uuid() -> None: + """file_id should be a full UUID hex string (no truncation).""" + resolved = [MagicMock(filename="f.jpg")] + tasks = [_make_task()] + + captured: list[dict[str, Any]] = [] + + def _sign(object_list: list[dict[str, Any]]) -> MagicMock: + captured.extend(object_list) + return _make_sign_response([{"object_key": "k", "url": "u"}]) + + mock_client = MagicMock() + mock_client.v1.object.sign = _sign + + batch_sign(mock_client, tasks, resolved) # type: ignore[arg-type] + + assert len(captured) == 1 + file_id = captured[0]["file_id"] + assert file_id.startswith("sdk-") + hex_part = file_id[len("sdk-"):] + assert len(hex_part) == 32, f"Expected 32-char hex, got {len(hex_part)}: {hex_part}" + + +def test_batch_sign_api_failure() -> None: + sign_resp = MagicMock() + sign_resp.status = 1 + sign_resp.error = "Unauthorized" + + mock_client = MagicMock() + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + with pytest.raises(UploadError, match="Sign failed"): + batch_sign(mock_client, [_make_task()], [MagicMock(filename="f.jpg")]) # type: ignore[arg-type] + + +def test_batch_sign_count_mismatch(tmp_path: Path) -> None: + resolved = [_make_resolved(tmp_path, "a.jpg"), _make_resolved(tmp_path, "b.jpg")] + tasks = [_make_task(0, 0, "a.jpg"), _make_task(0, 1, "b.jpg")] + + # Returns only 1 item for 2 tasks + sign_resp = _make_sign_response([{"object_key": "k1", "url": "u1"}]) + mock_client = MagicMock() + mock_client.v1.object.sign = MagicMock(return_value=sign_resp) + + with pytest.raises(UploadError, match="expected 2"): + batch_sign(mock_client, tasks, resolved) + + +# ── s3_post_upload (sync) ───────────────────────────────────────────────────── + + +def _mock_httpx_response(status_code: int) -> MagicMock: + resp = MagicMock(spec=httpx.Response) + resp.status_code = status_code + resp.text = "error text" + return resp + + +def test_s3_post_upload_success_204(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + with patch("everos.lib._upload.httpx.post", return_value=_mock_httpx_response(204)) as mock_post: + result = s3_post_upload(signed) + + assert result.object_key == signed.object_key + assert result.filename == "photo.jpg" + assert mock_post.call_count == 1 + + +def test_s3_post_upload_success_200(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + with patch("everos.lib._upload.httpx.post", return_value=_mock_httpx_response(200)): + result = s3_post_upload(signed) + assert result.object_key == signed.object_key + + +def test_s3_post_upload_4xx_no_retry(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + with patch("everos.lib._upload.httpx.post", return_value=_mock_httpx_response(403)) as mock_post: + with pytest.raises(UploadError, match="HTTP 403"): + s3_post_upload(signed) + assert mock_post.call_count == 1 # no retry on 4xx + + +def test_s3_post_upload_5xx_retries_and_fails(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + with patch("everos.lib._upload.httpx.post", return_value=_mock_httpx_response(503)) as mock_post: + with patch("everos.lib._upload.time.sleep"): + with pytest.raises(UploadError, match="after 3 attempts"): + s3_post_upload(signed) + assert mock_post.call_count == _MAX_UPLOAD_RETRIES + + +def test_s3_post_upload_5xx_then_success(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + responses = [_mock_httpx_response(503), _mock_httpx_response(204)] + with patch("everos.lib._upload.httpx.post", side_effect=responses) as mock_post: + with patch("everos.lib._upload.time.sleep"): + result = s3_post_upload(signed) + assert mock_post.call_count == 2 + assert result.object_key == signed.object_key + + +def test_s3_post_upload_timeout_retries(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + with patch( + "everos.lib._upload.httpx.post", + side_effect=httpx.TimeoutException("timeout"), + ) as mock_post: + with patch("everos.lib._upload.time.sleep"): + with pytest.raises(UploadError, match="after 3 attempts"): + s3_post_upload(signed) + assert mock_post.call_count == _MAX_UPLOAD_RETRIES + + +def test_s3_post_upload_sync_backoff_called(tmp_path: Path) -> None: + """Backoff sleep is called between retries (not after last attempt).""" + signed = _make_signed_file(tmp_path) + with patch("everos.lib._upload.httpx.post", return_value=_mock_httpx_response(503)): + with patch("everos.lib._upload.time.sleep") as mock_sleep: + with pytest.raises(UploadError): + s3_post_upload(signed) + # 3 attempts → 2 backoffs + assert mock_sleep.call_count == _MAX_UPLOAD_RETRIES - 1 + mock_sleep.assert_called_with(_RETRY_BACKOFF_S) + + +# ── async_batch_sign ────────────────────────────────────────────────────────── + + +async def test_async_batch_sign_success(tmp_path: Path) -> None: + resolved = [_make_resolved(tmp_path)] + tasks = [_make_task()] + + sign_resp = _make_sign_response([ + {"object_key": "uploads/uuid/photo.jpg", "url": "https://s3.example.com/", "fields": {"p": "q"}}, + ]) + + mock_client = MagicMock() + mock_client.v1.object.sign = AsyncMock(return_value=sign_resp) + + signed = await async_batch_sign(mock_client, tasks, resolved) + assert len(signed) == 1 + assert signed[0].object_key == "uploads/uuid/photo.jpg" + + +async def test_async_batch_sign_failure() -> None: + sign_resp = MagicMock() + sign_resp.status = 1 + sign_resp.error = "Forbidden" + mock_client = MagicMock() + mock_client.v1.object.sign = AsyncMock(return_value=sign_resp) + + with pytest.raises(UploadError, match="Sign failed"): + await async_batch_sign(mock_client, [_make_task()], [MagicMock(filename="x.jpg")]) # type: ignore[arg-type] + + +# ── async_s3_post_upload ────────────────────────────────────────────────────── + + +async def test_async_s3_post_upload_success(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + + mock_resp = MagicMock(spec=httpx.Response) + mock_resp.status_code = 204 + + mock_http = AsyncMock() + mock_http.post = AsyncMock(return_value=mock_resp) + mock_http.__aenter__ = AsyncMock(return_value=mock_http) + mock_http.__aexit__ = AsyncMock(return_value=False) + + with patch("everos.lib._upload.httpx.AsyncClient", return_value=mock_http): + result = await async_s3_post_upload(signed) + + assert result.object_key == signed.object_key + + +async def test_async_s3_post_upload_4xx_no_retry(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + + mock_resp = MagicMock(spec=httpx.Response) + mock_resp.status_code = 403 + mock_resp.text = "forbidden" + + mock_http = AsyncMock() + mock_http.post = AsyncMock(return_value=mock_resp) + mock_http.__aenter__ = AsyncMock(return_value=mock_http) + mock_http.__aexit__ = AsyncMock(return_value=False) + + with patch("everos.lib._upload.httpx.AsyncClient", return_value=mock_http): + with pytest.raises(UploadError, match="HTTP 403"): + await async_s3_post_upload(signed) + + assert mock_http.post.call_count == 1 + + +async def test_async_s3_post_upload_5xx_retries_all_fail(tmp_path: Path) -> None: + # asyncio is imported locally inside the function, so patch via the asyncio module directly + signed = _make_signed_file(tmp_path) + + mock_resp = MagicMock(spec=httpx.Response) + mock_resp.status_code = 503 + + mock_http = AsyncMock() + mock_http.post = AsyncMock(return_value=mock_resp) + mock_http.__aenter__ = AsyncMock(return_value=mock_http) + mock_http.__aexit__ = AsyncMock(return_value=False) + + with patch("everos.lib._upload.httpx.AsyncClient", return_value=mock_http): + with patch("everos.lib._upload.asyncio.sleep", new_callable=AsyncMock): + with pytest.raises(UploadError, match="after 3 attempts"): + await async_s3_post_upload(signed) + + assert mock_http.post.call_count == _MAX_UPLOAD_RETRIES + + +async def test_async_s3_post_upload_backoff_called(tmp_path: Path) -> None: + signed = _make_signed_file(tmp_path) + + mock_resp = MagicMock(spec=httpx.Response) + mock_resp.status_code = 503 + + mock_http = AsyncMock() + mock_http.post = AsyncMock(return_value=mock_resp) + mock_http.__aenter__ = AsyncMock(return_value=mock_http) + mock_http.__aexit__ = AsyncMock(return_value=False) + + with patch("everos.lib._upload.httpx.AsyncClient", return_value=mock_http): + with patch("everos.lib._upload.asyncio.sleep", new_callable=AsyncMock) as mock_sleep: + with pytest.raises(UploadError): + await async_s3_post_upload(signed) + + # Count only calls with our specific backoff value (session-scoped event loop may + # call asyncio.sleep internally with other arguments, e.g. 0) + backoff_calls = [c for c in mock_sleep.call_args_list if c == call(_RETRY_BACKOFF_S)] + assert len(backoff_calls) == _MAX_UPLOAD_RETRIES - 1 diff --git a/tests/test_client.py b/tests/test_client.py index c99ff5f..f067aa0 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -19,11 +19,11 @@ from respx import MockRouter from pydantic import ValidationError -from everos import Everos, AsyncEveros, APIResponseValidationError +from everos import EverOS, AsyncEverOS, APIResponseValidationError from everos._types import Omit from everos._utils import asyncify from everos._models import BaseModel, FinalRequestOptions -from everos._exceptions import EverosError, APIStatusError, APITimeoutError, APIResponseValidationError +from everos._exceptions import EverOSError, APIStatusError, APITimeoutError, APIResponseValidationError from everos._base_client import ( DEFAULT_TIMEOUT, HTTPX_DEFAULT_TIMEOUT, @@ -103,7 +103,7 @@ async def _make_async_iterator(iterable: Iterable[T], counter: Optional[Counter] yield item -def _get_open_connections(client: Everos | AsyncEveros) -> int: +def _get_open_connections(client: EverOS | AsyncEverOS) -> int: transport = client._client._transport assert isinstance(transport, httpx.HTTPTransport) or isinstance(transport, httpx.AsyncHTTPTransport) @@ -111,9 +111,9 @@ def _get_open_connections(client: Everos | AsyncEveros) -> int: return len(pool._requests) -class TestEveros: +class TestEverOS: @pytest.mark.respx(base_url=base_url) - def test_raw_response(self, respx_mock: MockRouter, client: Everos) -> None: + def test_raw_response(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) response = client.post("/foo", cast_to=httpx.Response) @@ -122,7 +122,7 @@ def test_raw_response(self, respx_mock: MockRouter, client: Everos) -> None: assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Everos) -> None: + def test_raw_response_for_binary(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) @@ -132,7 +132,7 @@ def test_raw_response_for_binary(self, respx_mock: MockRouter, client: Everos) - assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self, client: Everos) -> None: + def test_copy(self, client: EverOS) -> None: copied = client.copy() assert id(copied) != id(client) @@ -140,7 +140,7 @@ def test_copy(self, client: Everos) -> None: assert copied.api_key == "another My API Key" assert client.api_key == "My API Key" - def test_copy_default_options(self, client: Everos) -> None: + def test_copy_default_options(self, client: EverOS) -> None: # options that have a default are overridden correctly copied = client.copy(max_retries=7) assert copied.max_retries == 7 @@ -157,7 +157,7 @@ def test_copy_default_options(self, client: Everos) -> None: assert isinstance(client.timeout, httpx.Timeout) def test_copy_default_headers(self) -> None: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -192,7 +192,7 @@ def test_copy_default_headers(self) -> None: client.close() def test_copy_default_query(self) -> None: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -229,7 +229,7 @@ def test_copy_default_query(self) -> None: client.close() - def test_copy_signature(self, client: Everos) -> None: + def test_copy_signature(self, client: EverOS) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. @@ -246,7 +246,7 @@ def test_copy_signature(self, client: Everos) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self, client: Everos) -> None: + def test_copy_build_request(self, client: EverOS) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: @@ -308,7 +308,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - def test_request_timeout(self, client: Everos) -> None: + def test_request_timeout(self, client: EverOS) -> None: request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT @@ -318,7 +318,7 @@ def test_request_timeout(self, client: Everos) -> None: assert timeout == httpx.Timeout(100.0) def test_client_timeout_option(self) -> None: - client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)) + client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0)) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore @@ -329,7 +329,7 @@ def test_client_timeout_option(self) -> None: def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used with httpx.Client(timeout=None) as http_client: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -341,7 +341,7 @@ def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default with httpx.Client() as http_client: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -353,7 +353,7 @@ def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored with httpx.Client(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -366,7 +366,7 @@ def test_http_client_timeout_option(self) -> None: async def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): async with httpx.AsyncClient() as http_client: - Everos( + EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -374,14 +374,14 @@ async def test_invalid_http_client(self) -> None: ) def test_default_headers_option(self) -> None: - test_client = Everos( + test_client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - test_client2 = Everos( + test_client2 = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -398,17 +398,17 @@ def test_default_headers_option(self) -> None: test_client2.close() def test_validate_headers(self) -> None: - client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == f"Bearer {api_key}" - with pytest.raises(EverosError): + with pytest.raises(EverOSError): with update_env(**{"EVEROS_API_KEY": Omit()}): - client2 = Everos(base_url=base_url, api_key=None, _strict_response_validation=True) + client2 = EverOS(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 def test_default_query_option(self) -> None: - client = Everos( + client = EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -427,7 +427,7 @@ def test_default_query_option(self) -> None: client.close() - def test_request_extra_json(self, client: Everos) -> None: + def test_request_extra_json(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -461,7 +461,7 @@ def test_request_extra_json(self, client: Everos) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self, client: Everos) -> None: + def test_request_extra_headers(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -483,7 +483,7 @@ def test_request_extra_headers(self, client: Everos) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self, client: Everos) -> None: + def test_request_extra_query(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -524,7 +524,7 @@ def test_request_extra_query(self, client: Everos) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, client: Everos) -> None: + def test_multipart_repeating_array(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions.construct( method="post", @@ -554,7 +554,7 @@ def test_multipart_repeating_array(self, client: Everos) -> None: ] @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload(self, respx_mock: MockRouter, client: Everos) -> None: + def test_binary_content_upload(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." @@ -579,7 +579,7 @@ def mock_handler(request: httpx.Request) -> httpx.Response: assert counter.value == 0, "the request body should not have been read" return httpx.Response(200, content=request.read()) - with Everos( + with EverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -598,7 +598,7 @@ def mock_handler(request: httpx.Request) -> httpx.Response: assert counter.value == 1 @pytest.mark.respx(base_url=base_url) - def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: Everos) -> None: + def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." @@ -618,7 +618,7 @@ def test_binary_content_upload_with_body_is_deprecated(self, respx_mock: MockRou assert response.content == file_content @pytest.mark.respx(base_url=base_url) - def test_basic_union_response(self, respx_mock: MockRouter, client: Everos) -> None: + def test_basic_union_response(self, respx_mock: MockRouter, client: EverOS) -> None: class Model1(BaseModel): name: str @@ -632,7 +632,7 @@ class Model2(BaseModel): assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - def test_union_response_different_types(self, respx_mock: MockRouter, client: Everos) -> None: + def test_union_response_different_types(self, respx_mock: MockRouter, client: EverOS) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -654,7 +654,7 @@ class Model2(BaseModel): assert response.foo == 1 @pytest.mark.respx(base_url=base_url) - def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: Everos) -> None: + def test_non_application_json_content_type_for_json_data(self, respx_mock: MockRouter, client: EverOS) -> None: """ Response that sets Content-Type to something other than application/json but returns json data """ @@ -675,7 +675,7 @@ class Model(BaseModel): assert response.foo == 2 def test_base_url_setter(self) -> None: - client = Everos(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) + client = EverOS(base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True) assert client.base_url == "https://example.com/from_init/" client.base_url = "https://example.com/from_setter" # type: ignore[assignment] @@ -685,25 +685,15 @@ def test_base_url_setter(self) -> None: client.close() def test_base_url_env(self) -> None: - with update_env(EVEROS_BASE_URL="http://localhost:5000/from/env"): - client = Everos(api_key=api_key, _strict_response_validation=True) + with update_env(EVER_OS_BASE_URL="http://localhost:5000/from/env"): + client = EverOS(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" - # explicit environment arg requires explicitness - with update_env(EVEROS_BASE_URL="http://localhost:5000/from/env"): - with pytest.raises(ValueError, match=r"you must pass base_url=None"): - Everos(api_key=api_key, _strict_response_validation=True, environment="production") - - client = Everos(base_url=None, api_key=api_key, _strict_response_validation=True, environment="production") - assert str(client.base_url).startswith("http://localhost:9527") - - client.close() - @pytest.mark.parametrize( "client", [ - Everos(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), - Everos( + EverOS(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), + EverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -712,7 +702,7 @@ def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_trailing_slash(self, client: Everos) -> None: + def test_base_url_trailing_slash(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -726,8 +716,8 @@ def test_base_url_trailing_slash(self, client: Everos) -> None: @pytest.mark.parametrize( "client", [ - Everos(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), - Everos( + EverOS(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), + EverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -736,7 +726,7 @@ def test_base_url_trailing_slash(self, client: Everos) -> None: ], ids=["standard", "custom http client"], ) - def test_base_url_no_trailing_slash(self, client: Everos) -> None: + def test_base_url_no_trailing_slash(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -750,8 +740,8 @@ def test_base_url_no_trailing_slash(self, client: Everos) -> None: @pytest.mark.parametrize( "client", [ - Everos(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), - Everos( + EverOS(base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True), + EverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -760,7 +750,7 @@ def test_base_url_no_trailing_slash(self, client: Everos) -> None: ], ids=["standard", "custom http client"], ) - def test_absolute_request_url(self, client: Everos) -> None: + def test_absolute_request_url(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -772,7 +762,7 @@ def test_absolute_request_url(self, client: Everos) -> None: client.close() def test_copied_client_does_not_close_http(self) -> None: - test_client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True) + test_client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not test_client.is_closed() copied = test_client.copy() @@ -783,7 +773,7 @@ def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() def test_client_context_manager(self) -> None: - test_client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True) + test_client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -791,7 +781,7 @@ def test_client_context_manager(self) -> None: assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - def test_client_response_validation_error(self, respx_mock: MockRouter, client: Everos) -> None: + def test_client_response_validation_error(self, respx_mock: MockRouter, client: EverOS) -> None: class Model(BaseModel): foo: str @@ -804,7 +794,7 @@ class Model(BaseModel): def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) + EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None)) @pytest.mark.respx(base_url=base_url) def test_received_text_for_expected_json(self, respx_mock: MockRouter) -> None: @@ -813,12 +803,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): strict_client.get("/foo", cast_to=Model) - non_strict_client = Everos(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = EverOS(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -849,7 +839,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) def test_parse_retry_after_header( - self, remaining_retries: int, retry_after: str, timeout: float, client: Everos + self, remaining_retries: int, retry_after: str, timeout: float, client: EverOS ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -858,11 +848,11 @@ def test_parse_retry_after_header( @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: Everos) -> None: + def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/api/v1/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - client.v1.memories.with_streaming_response.create( + client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -877,11 +867,11 @@ def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, clien @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: Everos) -> None: + def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client: EverOS) -> None: respx_mock.post("/api/v1/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - client.v1.memories.with_streaming_response.create( + client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -899,7 +889,7 @@ def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, client @pytest.mark.parametrize("failure_mode", ["status", "exception"]) def test_retries_taken( self, - client: Everos, + client: EverOS, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -919,7 +909,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = client.v1.memories.with_raw_response.create( + response = client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -937,7 +927,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_omit_retry_count_header( - self, client: Everos, failures_before_success: int, respx_mock: MockRouter + self, client: EverOS, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -952,7 +942,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = client.v1.memories.with_raw_response.create( + response = client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -970,7 +960,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) def test_overwrite_retry_count_header( - self, client: Everos, failures_before_success: int, respx_mock: MockRouter + self, client: EverOS, failures_before_success: int, respx_mock: MockRouter ) -> None: client = client.with_options(max_retries=4) @@ -985,7 +975,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = client.v1.memories.with_raw_response.create( + response = client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -1030,7 +1020,7 @@ def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - def test_follow_redirects(self, respx_mock: MockRouter, client: Everos) -> None: + def test_follow_redirects(self, respx_mock: MockRouter, client: EverOS) -> None: # Test that the default follow_redirects=True allows following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) @@ -1042,7 +1032,7 @@ def test_follow_redirects(self, respx_mock: MockRouter, client: Everos) -> None: assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Everos) -> None: + def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: EverOS) -> None: # Test that follow_redirects=False prevents following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) @@ -1055,9 +1045,9 @@ def test_follow_redirects_disabled(self, respx_mock: MockRouter, client: Everos) assert exc_info.value.response.headers["Location"] == f"{base_url}/redirected" -class TestAsyncEveros: +class TestAsyncEverOS: @pytest.mark.respx(base_url=base_url) - async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: respx_mock.post("/foo").mock(return_value=httpx.Response(200, json={"foo": "bar"})) response = await async_client.post("/foo", cast_to=httpx.Response) @@ -1066,7 +1056,7 @@ async def test_raw_response(self, respx_mock: MockRouter, async_client: AsyncEve assert response.json() == {"foo": "bar"} @pytest.mark.respx(base_url=base_url) - async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: respx_mock.post("/foo").mock( return_value=httpx.Response(200, headers={"Content-Type": "application/binary"}, content='{"foo": "bar"}') ) @@ -1076,7 +1066,7 @@ async def test_raw_response_for_binary(self, respx_mock: MockRouter, async_clien assert isinstance(response, httpx.Response) assert response.json() == {"foo": "bar"} - def test_copy(self, async_client: AsyncEveros) -> None: + def test_copy(self, async_client: AsyncEverOS) -> None: copied = async_client.copy() assert id(copied) != id(async_client) @@ -1084,7 +1074,7 @@ def test_copy(self, async_client: AsyncEveros) -> None: assert copied.api_key == "another My API Key" assert async_client.api_key == "My API Key" - def test_copy_default_options(self, async_client: AsyncEveros) -> None: + def test_copy_default_options(self, async_client: AsyncEverOS) -> None: # options that have a default are overridden correctly copied = async_client.copy(max_retries=7) assert copied.max_retries == 7 @@ -1101,7 +1091,7 @@ def test_copy_default_options(self, async_client: AsyncEveros) -> None: assert isinstance(async_client.timeout, httpx.Timeout) async def test_copy_default_headers(self) -> None: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) assert client.default_headers["X-Foo"] == "bar" @@ -1136,7 +1126,7 @@ async def test_copy_default_headers(self) -> None: await client.close() async def test_copy_default_query(self) -> None: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"foo": "bar"} ) assert _get_params(client)["foo"] == "bar" @@ -1173,7 +1163,7 @@ async def test_copy_default_query(self) -> None: await client.close() - def test_copy_signature(self, async_client: AsyncEveros) -> None: + def test_copy_signature(self, async_client: AsyncEverOS) -> None: # ensure the same parameters that can be passed to the client are defined in the `.copy()` method init_signature = inspect.signature( # mypy doesn't like that we access the `__init__` property. @@ -1190,7 +1180,7 @@ def test_copy_signature(self, async_client: AsyncEveros) -> None: assert copy_param is not None, f"copy() signature is missing the {name} param" @pytest.mark.skipif(sys.version_info >= (3, 10), reason="fails because of a memory leak that started from 3.12") - def test_copy_build_request(self, async_client: AsyncEveros) -> None: + def test_copy_build_request(self, async_client: AsyncEverOS) -> None: options = FinalRequestOptions(method="get", url="/foo") def build_request(options: FinalRequestOptions) -> None: @@ -1252,7 +1242,7 @@ def add_leak(leaks: list[tracemalloc.StatisticDiff], diff: tracemalloc.Statistic print(frame) raise AssertionError() - async def test_request_timeout(self, async_client: AsyncEveros) -> None: + async def test_request_timeout(self, async_client: AsyncEverOS) -> None: request = async_client._build_request(FinalRequestOptions(method="get", url="/foo")) timeout = httpx.Timeout(**request.extensions["timeout"]) # type: ignore assert timeout == DEFAULT_TIMEOUT @@ -1264,7 +1254,7 @@ async def test_request_timeout(self, async_client: AsyncEveros) -> None: assert timeout == httpx.Timeout(100.0) async def test_client_timeout_option(self) -> None: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, timeout=httpx.Timeout(0) ) @@ -1277,7 +1267,7 @@ async def test_client_timeout_option(self) -> None: async def test_http_client_timeout_option(self) -> None: # custom timeout given to the httpx client should be used async with httpx.AsyncClient(timeout=None) as http_client: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -1289,7 +1279,7 @@ async def test_http_client_timeout_option(self) -> None: # no timeout given to the httpx client should not use the httpx default async with httpx.AsyncClient() as http_client: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -1301,7 +1291,7 @@ async def test_http_client_timeout_option(self) -> None: # explicitly passing the default timeout currently results in it being ignored async with httpx.AsyncClient(timeout=HTTPX_DEFAULT_TIMEOUT) as http_client: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, http_client=http_client ) @@ -1314,7 +1304,7 @@ async def test_http_client_timeout_option(self) -> None: def test_invalid_http_client(self) -> None: with pytest.raises(TypeError, match="Invalid `http_client` arg"): with httpx.Client() as http_client: - AsyncEveros( + AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1322,14 +1312,14 @@ def test_invalid_http_client(self) -> None: ) async def test_default_headers_option(self) -> None: - test_client = AsyncEveros( + test_client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_headers={"X-Foo": "bar"} ) request = test_client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("x-foo") == "bar" assert request.headers.get("x-stainless-lang") == "python" - test_client2 = AsyncEveros( + test_client2 = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1346,17 +1336,17 @@ async def test_default_headers_option(self) -> None: await test_client2.close() def test_validate_headers(self) -> None: - client = AsyncEveros(base_url=base_url, api_key=api_key, _strict_response_validation=True) + client = AsyncEverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) assert request.headers.get("Authorization") == f"Bearer {api_key}" - with pytest.raises(EverosError): + with pytest.raises(EverOSError): with update_env(**{"EVEROS_API_KEY": Omit()}): - client2 = AsyncEveros(base_url=base_url, api_key=None, _strict_response_validation=True) + client2 = AsyncEverOS(base_url=base_url, api_key=None, _strict_response_validation=True) _ = client2 async def test_default_query_option(self) -> None: - client = AsyncEveros( + client = AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, default_query={"query_param": "bar"} ) request = client._build_request(FinalRequestOptions(method="get", url="/foo")) @@ -1375,7 +1365,7 @@ async def test_default_query_option(self) -> None: await client.close() - def test_request_extra_json(self, client: Everos) -> None: + def test_request_extra_json(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1409,7 +1399,7 @@ def test_request_extra_json(self, client: Everos) -> None: data = json.loads(request.content.decode("utf-8")) assert data == {"foo": "bar", "baz": None} - def test_request_extra_headers(self, client: Everos) -> None: + def test_request_extra_headers(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1431,7 +1421,7 @@ def test_request_extra_headers(self, client: Everos) -> None: ) assert request.headers.get("X-Bar") == "false" - def test_request_extra_query(self, client: Everos) -> None: + def test_request_extra_query(self, client: EverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1472,7 +1462,7 @@ def test_request_extra_query(self, client: Everos) -> None: params = dict(request.url.params) assert params == {"foo": "2"} - def test_multipart_repeating_array(self, async_client: AsyncEveros) -> None: + def test_multipart_repeating_array(self, async_client: AsyncEverOS) -> None: request = async_client._build_request( FinalRequestOptions.construct( method="post", @@ -1502,7 +1492,7 @@ def test_multipart_repeating_array(self, async_client: AsyncEveros) -> None: ] @pytest.mark.respx(base_url=base_url) - async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_binary_content_upload(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: respx_mock.post("/upload").mock(side_effect=mirror_request_content) file_content = b"Hello, this is a test file." @@ -1527,7 +1517,7 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: assert counter.value == 0, "the request body should not have been read" return httpx.Response(200, content=await request.aread()) - async with AsyncEveros( + async with AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, @@ -1547,7 +1537,7 @@ async def mock_handler(request: httpx.Request) -> httpx.Response: @pytest.mark.respx(base_url=base_url) async def test_binary_content_upload_with_body_is_deprecated( - self, respx_mock: MockRouter, async_client: AsyncEveros + self, respx_mock: MockRouter, async_client: AsyncEverOS ) -> None: respx_mock.post("/upload").mock(side_effect=mirror_request_content) @@ -1568,7 +1558,7 @@ async def test_binary_content_upload_with_body_is_deprecated( assert response.content == file_content @pytest.mark.respx(base_url=base_url) - async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_basic_union_response(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: class Model1(BaseModel): name: str @@ -1582,7 +1572,7 @@ class Model2(BaseModel): assert response.foo == "bar" @pytest.mark.respx(base_url=base_url) - async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_union_response_different_types(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: """Union of objects with the same field name using a different type""" class Model1(BaseModel): @@ -1605,7 +1595,7 @@ class Model2(BaseModel): @pytest.mark.respx(base_url=base_url) async def test_non_application_json_content_type_for_json_data( - self, respx_mock: MockRouter, async_client: AsyncEveros + self, respx_mock: MockRouter, async_client: AsyncEverOS ) -> None: """ Response that sets Content-Type to something other than application/json but returns json data @@ -1627,7 +1617,7 @@ class Model(BaseModel): assert response.foo == 2 async def test_base_url_setter(self) -> None: - client = AsyncEveros( + client = AsyncEverOS( base_url="https://example.com/from_init", api_key=api_key, _strict_response_validation=True ) assert client.base_url == "https://example.com/from_init/" @@ -1639,29 +1629,17 @@ async def test_base_url_setter(self) -> None: await client.close() async def test_base_url_env(self) -> None: - with update_env(EVEROS_BASE_URL="http://localhost:5000/from/env"): - client = AsyncEveros(api_key=api_key, _strict_response_validation=True) + with update_env(EVER_OS_BASE_URL="http://localhost:5000/from/env"): + client = AsyncEverOS(api_key=api_key, _strict_response_validation=True) assert client.base_url == "http://localhost:5000/from/env/" - # explicit environment arg requires explicitness - with update_env(EVEROS_BASE_URL="http://localhost:5000/from/env"): - with pytest.raises(ValueError, match=r"you must pass base_url=None"): - AsyncEveros(api_key=api_key, _strict_response_validation=True, environment="production") - - client = AsyncEveros( - base_url=None, api_key=api_key, _strict_response_validation=True, environment="production" - ) - assert str(client.base_url).startswith("http://localhost:9527") - - await client.close() - @pytest.mark.parametrize( "client", [ - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -1670,7 +1648,7 @@ async def test_base_url_env(self) -> None: ], ids=["standard", "custom http client"], ) - async def test_base_url_trailing_slash(self, client: AsyncEveros) -> None: + async def test_base_url_trailing_slash(self, client: AsyncEverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1684,10 +1662,10 @@ async def test_base_url_trailing_slash(self, client: AsyncEveros) -> None: @pytest.mark.parametrize( "client", [ - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -1696,7 +1674,7 @@ async def test_base_url_trailing_slash(self, client: AsyncEveros) -> None: ], ids=["standard", "custom http client"], ) - async def test_base_url_no_trailing_slash(self, client: AsyncEveros) -> None: + async def test_base_url_no_trailing_slash(self, client: AsyncEverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1710,10 +1688,10 @@ async def test_base_url_no_trailing_slash(self, client: AsyncEveros) -> None: @pytest.mark.parametrize( "client", [ - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True ), - AsyncEveros( + AsyncEverOS( base_url="http://localhost:5000/custom/path/", api_key=api_key, _strict_response_validation=True, @@ -1722,7 +1700,7 @@ async def test_base_url_no_trailing_slash(self, client: AsyncEveros) -> None: ], ids=["standard", "custom http client"], ) - async def test_absolute_request_url(self, client: AsyncEveros) -> None: + async def test_absolute_request_url(self, client: AsyncEverOS) -> None: request = client._build_request( FinalRequestOptions( method="post", @@ -1734,7 +1712,7 @@ async def test_absolute_request_url(self, client: AsyncEveros) -> None: await client.close() async def test_copied_client_does_not_close_http(self) -> None: - test_client = AsyncEveros(base_url=base_url, api_key=api_key, _strict_response_validation=True) + test_client = AsyncEverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) assert not test_client.is_closed() copied = test_client.copy() @@ -1746,7 +1724,7 @@ async def test_copied_client_does_not_close_http(self) -> None: assert not test_client.is_closed() async def test_client_context_manager(self) -> None: - test_client = AsyncEveros(base_url=base_url, api_key=api_key, _strict_response_validation=True) + test_client = AsyncEverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) async with test_client as c2: assert c2 is test_client assert not c2.is_closed() @@ -1754,7 +1732,7 @@ async def test_client_context_manager(self) -> None: assert test_client.is_closed() @pytest.mark.respx(base_url=base_url) - async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_client_response_validation_error(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: class Model(BaseModel): foo: str @@ -1767,7 +1745,7 @@ class Model(BaseModel): async def test_client_max_retries_validation(self) -> None: with pytest.raises(TypeError, match=r"max_retries cannot be None"): - AsyncEveros( + AsyncEverOS( base_url=base_url, api_key=api_key, _strict_response_validation=True, max_retries=cast(Any, None) ) @@ -1778,12 +1756,12 @@ class Model(BaseModel): respx_mock.get("/foo").mock(return_value=httpx.Response(200, text="my-custom-format")) - strict_client = AsyncEveros(base_url=base_url, api_key=api_key, _strict_response_validation=True) + strict_client = AsyncEverOS(base_url=base_url, api_key=api_key, _strict_response_validation=True) with pytest.raises(APIResponseValidationError): await strict_client.get("/foo", cast_to=Model) - non_strict_client = AsyncEveros(base_url=base_url, api_key=api_key, _strict_response_validation=False) + non_strict_client = AsyncEverOS(base_url=base_url, api_key=api_key, _strict_response_validation=False) response = await non_strict_client.get("/foo", cast_to=Model) assert isinstance(response, str) # type: ignore[unreachable] @@ -1814,7 +1792,7 @@ class Model(BaseModel): ) @mock.patch("time.time", mock.MagicMock(return_value=1696004797)) async def test_parse_retry_after_header( - self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncEveros + self, remaining_retries: int, retry_after: str, timeout: float, async_client: AsyncEverOS ) -> None: headers = httpx.Headers({"retry-after": retry_after}) options = FinalRequestOptions(method="get", url="/foo", max_retries=3) @@ -1823,11 +1801,11 @@ async def test_parse_retry_after_header( @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: respx_mock.post("/api/v1/memories").mock(side_effect=httpx.TimeoutException("Test timeout error")) with pytest.raises(APITimeoutError): - await async_client.v1.memories.with_streaming_response.create( + await async_client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -1842,11 +1820,11 @@ async def test_retrying_timeout_errors_doesnt_leak(self, respx_mock: MockRouter, @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) - async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: respx_mock.post("/api/v1/memories").mock(return_value=httpx.Response(500)) with pytest.raises(APIStatusError): - await async_client.v1.memories.with_streaming_response.create( + await async_client.v1.memories.with_streaming_response.add( messages=[ { "content": "x", @@ -1864,7 +1842,7 @@ async def test_retrying_status_errors_doesnt_leak(self, respx_mock: MockRouter, @pytest.mark.parametrize("failure_mode", ["status", "exception"]) async def test_retries_taken( self, - async_client: AsyncEveros, + async_client: AsyncEverOS, failures_before_success: int, failure_mode: Literal["status", "exception"], respx_mock: MockRouter, @@ -1884,7 +1862,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = await client.v1.memories.with_raw_response.create( + response = await client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -1902,7 +1880,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_omit_retry_count_header( - self, async_client: AsyncEveros, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncEverOS, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) @@ -1917,7 +1895,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = await client.v1.memories.with_raw_response.create( + response = await client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -1935,7 +1913,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: @mock.patch("everos._base_client.BaseClient._calculate_retry_timeout", _low_retry_timeout) @pytest.mark.respx(base_url=base_url) async def test_overwrite_retry_count_header( - self, async_client: AsyncEveros, failures_before_success: int, respx_mock: MockRouter + self, async_client: AsyncEverOS, failures_before_success: int, respx_mock: MockRouter ) -> None: client = async_client.with_options(max_retries=4) @@ -1950,7 +1928,7 @@ def retry_handler(_request: httpx.Request) -> httpx.Response: respx_mock.post("/api/v1/memories").mock(side_effect=retry_handler) - response = await client.v1.memories.with_raw_response.create( + response = await client.v1.memories.with_raw_response.add( messages=[ { "content": "x", @@ -1999,7 +1977,7 @@ async def test_default_client_creation(self) -> None: ) @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_follow_redirects(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: # Test that the default follow_redirects=True allows following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) @@ -2011,7 +1989,7 @@ async def test_follow_redirects(self, respx_mock: MockRouter, async_client: Asyn assert response.json() == {"status": "ok"} @pytest.mark.respx(base_url=base_url) - async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncEveros) -> None: + async def test_follow_redirects_disabled(self, respx_mock: MockRouter, async_client: AsyncEverOS) -> None: # Test that follow_redirects=False prevents following redirects respx_mock.post("/redirect").mock( return_value=httpx.Response(302, headers={"Location": f"{base_url}/redirected"}) diff --git a/tests/test_response.py b/tests/test_response.py index 66bdc73..cb0786b 100644 --- a/tests/test_response.py +++ b/tests/test_response.py @@ -6,7 +6,7 @@ import pytest import pydantic -from everos import Everos, BaseModel, AsyncEveros +from everos import EverOS, BaseModel, AsyncEverOS from everos._response import ( APIResponse, BaseAPIResponse, @@ -56,7 +56,7 @@ def test_extract_response_type_binary_response() -> None: class PydanticModel(pydantic.BaseModel): ... -def test_response_parse_mismatched_basemodel(client: Everos) -> None: +def test_response_parse_mismatched_basemodel(client: EverOS) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -74,7 +74,7 @@ def test_response_parse_mismatched_basemodel(client: Everos) -> None: @pytest.mark.asyncio -async def test_async_response_parse_mismatched_basemodel(async_client: AsyncEveros) -> None: +async def test_async_response_parse_mismatched_basemodel(async_client: AsyncEverOS) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -91,7 +91,7 @@ async def test_async_response_parse_mismatched_basemodel(async_client: AsyncEver await response.parse(to=PydanticModel) -def test_response_parse_custom_stream(client: Everos) -> None: +def test_response_parse_custom_stream(client: EverOS) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo"), client=client, @@ -106,7 +106,7 @@ def test_response_parse_custom_stream(client: Everos) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_stream(async_client: AsyncEveros) -> None: +async def test_async_response_parse_custom_stream(async_client: AsyncEverOS) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo"), client=async_client, @@ -125,7 +125,7 @@ class CustomModel(BaseModel): bar: int -def test_response_parse_custom_model(client: Everos) -> None: +def test_response_parse_custom_model(client: EverOS) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -141,7 +141,7 @@ def test_response_parse_custom_model(client: Everos) -> None: @pytest.mark.asyncio -async def test_async_response_parse_custom_model(async_client: AsyncEveros) -> None: +async def test_async_response_parse_custom_model(async_client: AsyncEverOS) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -156,7 +156,7 @@ async def test_async_response_parse_custom_model(async_client: AsyncEveros) -> N assert obj.bar == 2 -def test_response_parse_annotated_type(client: Everos) -> None: +def test_response_parse_annotated_type(client: EverOS) -> None: response = APIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=client, @@ -173,7 +173,7 @@ def test_response_parse_annotated_type(client: Everos) -> None: assert obj.bar == 2 -async def test_async_response_parse_annotated_type(async_client: AsyncEveros) -> None: +async def test_async_response_parse_annotated_type(async_client: AsyncEverOS) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=json.dumps({"foo": "hello!", "bar": 2})), client=async_client, @@ -201,7 +201,7 @@ async def test_async_response_parse_annotated_type(async_client: AsyncEveros) -> ("FalSe", False), ], ) -def test_response_parse_bool(client: Everos, content: str, expected: bool) -> None: +def test_response_parse_bool(client: EverOS, content: str, expected: bool) -> None: response = APIResponse( raw=httpx.Response(200, content=content), client=client, @@ -226,7 +226,7 @@ def test_response_parse_bool(client: Everos, content: str, expected: bool) -> No ("FalSe", False), ], ) -async def test_async_response_parse_bool(client: AsyncEveros, content: str, expected: bool) -> None: +async def test_async_response_parse_bool(client: AsyncEverOS, content: str, expected: bool) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=content), client=client, @@ -245,7 +245,7 @@ class OtherModel(BaseModel): @pytest.mark.parametrize("client", [False], indirect=True) # loose validation -def test_response_parse_expect_model_union_non_json_content(client: Everos) -> None: +def test_response_parse_expect_model_union_non_json_content(client: EverOS) -> None: response = APIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=client, @@ -262,7 +262,7 @@ def test_response_parse_expect_model_union_non_json_content(client: Everos) -> N @pytest.mark.asyncio @pytest.mark.parametrize("async_client", [False], indirect=True) # loose validation -async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncEveros) -> None: +async def test_async_response_parse_expect_model_union_non_json_content(async_client: AsyncEverOS) -> None: response = AsyncAPIResponse( raw=httpx.Response(200, content=b"foo", headers={"Content-Type": "application/text"}), client=async_client, diff --git a/tests/test_streaming.py b/tests/test_streaming.py index 763de8b..dc7ea45 100644 --- a/tests/test_streaming.py +++ b/tests/test_streaming.py @@ -5,13 +5,13 @@ import httpx import pytest -from everos import Everos, AsyncEveros +from everos import EverOS, AsyncEverOS from everos._streaming import Stream, AsyncStream, ServerSentEvent @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_basic(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_basic(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: completion\n" yield b'data: {"foo":true}\n' @@ -28,7 +28,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_missing_event(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_data_missing_event(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b'data: {"foo":true}\n' yield b"\n" @@ -44,7 +44,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_event_missing_data(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_event_missing_data(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -60,7 +60,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_multiple_events(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"\n" @@ -82,7 +82,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_events_with_data(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_multiple_events_with_data(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo":true}\n' @@ -106,7 +106,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines_with_empty_line(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_multiple_data_lines_with_empty_line(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -128,7 +128,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_data_json_escaped_double_new_line(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_data_json_escaped_double_new_line(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b'data: {"foo": "my long\\n\\ncontent"}' @@ -145,7 +145,7 @@ def body() -> Iterator[bytes]: @pytest.mark.asyncio @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) -async def test_multiple_data_lines(sync: bool, client: Everos, async_client: AsyncEveros) -> None: +async def test_multiple_data_lines(sync: bool, client: EverOS, async_client: AsyncEverOS) -> None: def body() -> Iterator[bytes]: yield b"event: ping\n" yield b"data: {\n" @@ -165,8 +165,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_special_new_line_character( sync: bool, - client: Everos, - async_client: AsyncEveros, + client: EverOS, + async_client: AsyncEverOS, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":" culpa"}\n' @@ -196,8 +196,8 @@ def body() -> Iterator[bytes]: @pytest.mark.parametrize("sync", [True, False], ids=["sync", "async"]) async def test_multi_byte_character_multiple_chunks( sync: bool, - client: Everos, - async_client: AsyncEveros, + client: EverOS, + async_client: AsyncEverOS, ) -> None: def body() -> Iterator[bytes]: yield b'data: {"content":"' @@ -237,8 +237,8 @@ def make_event_iterator( content: Iterator[bytes], *, sync: bool, - client: Everos, - async_client: AsyncEveros, + client: EverOS, + async_client: AsyncEverOS, ) -> Iterator[ServerSentEvent] | AsyncIterator[ServerSentEvent]: if sync: return Stream(cast_to=object, client=client, response=httpx.Response(200, content=content))._iter_events() From f0aeb1f3820928ac8259334770321c5cda3e8165 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 20:12:31 +0000 Subject: [PATCH 2/2] feat(api): api update --- .github/workflows/publish-pypi.yml | 7 ++++--- .github/workflows/release-doctor.yml | 4 +--- .stats.yml | 2 +- CONTRIBUTING.md | 4 ++-- README.md | 6 +++--- bin/check-release-environment | 4 ---- bin/publish-pypi | 6 +++++- pyproject.toml | 6 +++--- src/everos/resources/v1/groups.py | 8 ++++---- src/everos/resources/v1/memories/agent.py | 8 ++++---- src/everos/resources/v1/memories/group.py | 8 ++++---- src/everos/resources/v1/memories/memories.py | 8 ++++---- src/everos/resources/v1/object.py | 8 ++++---- src/everos/resources/v1/senders.py | 8 ++++---- src/everos/resources/v1/settings.py | 8 ++++---- src/everos/resources/v1/tasks.py | 8 ++++---- src/everos/resources/v1/v1.py | 8 ++++---- 17 files changed, 55 insertions(+), 56 deletions(-) diff --git a/.github/workflows/publish-pypi.yml b/.github/workflows/publish-pypi.yml index c5ef6c0..36835e1 100644 --- a/.github/workflows/publish-pypi.yml +++ b/.github/workflows/publish-pypi.yml @@ -1,6 +1,6 @@ # This workflow is triggered when a GitHub release is created. # It can also be run manually to re-publish to PyPI in case it failed for some reason. -# You can run this workflow by navigating to https://www.github.com/everos/everos-python/actions/workflows/publish-pypi.yml +# You can run this workflow by navigating to https://www.github.com/evermemos/everos-python/actions/workflows/publish-pypi.yml name: Publish PyPI on: workflow_dispatch: @@ -12,6 +12,9 @@ jobs: publish: name: publish runs-on: ubuntu-latest + permissions: + contents: read + id-token: write steps: - uses: actions/checkout@v6 @@ -24,5 +27,3 @@ jobs: - name: Publish to PyPI run: | bash ./bin/publish-pypi - env: - PYPI_TOKEN: ${{ secrets.EVER_OS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.github/workflows/release-doctor.yml b/.github/workflows/release-doctor.yml index 13c4770..ed8b59c 100644 --- a/.github/workflows/release-doctor.yml +++ b/.github/workflows/release-doctor.yml @@ -9,7 +9,7 @@ jobs: release_doctor: name: release doctor runs-on: ubuntu-latest - if: github.repository == 'everos/everos-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') + if: github.repository == 'evermemos/everos-python' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || startsWith(github.head_ref, 'release-please') || github.head_ref == 'next') steps: - uses: actions/checkout@v6 @@ -17,5 +17,3 @@ jobs: - name: Check release environment run: | bash ./bin/check-release-environment - env: - PYPI_TOKEN: ${{ secrets.EVER_OS_PYPI_TOKEN || secrets.PYPI_TOKEN }} diff --git a/.stats.yml b/.stats.yml index c970a94..7973879 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 19 openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/evermind%2Feveros-85b37aa2a2bf87c039a27e33652dcfcab2b64f5e90a058e7c4a49b12a8094e02.yml openapi_spec_hash: 31f3985c07b07d8c3bd06e0622fd575e -config_hash: b447a6fa02f16b6e8a243cc143c14df9 +config_hash: 88946f527d4ca03aa70d121a51f89e1f diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ca5f33..67c907c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,7 +62,7 @@ If you’d like to use the repository from source, you can either install from g To install via git: ```sh -$ pip install git+ssh://git@github.com/everos/everos-python.git +$ pip install git+ssh://git@github.com/evermemos/everos-python.git ``` Alternatively, you can build from source and install the wheel file: @@ -113,7 +113,7 @@ the changes aren't made through the automated pipeline, you may want to make rel ### Publish with a GitHub workflow -You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/everos/everos-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. +You can release to package managers by using [the `Publish PyPI` GitHub action](https://www.github.com/evermemos/everos-python/actions/workflows/publish-pypi.yml). This requires a setup organization or repository secret to be set up. ### Publish manually diff --git a/README.md b/README.md index d3d3795..e8fd21f 100644 --- a/README.md +++ b/README.md @@ -312,9 +312,9 @@ memory = response.parse() # get the object that `v1.memories.add()` would have print(memory.data) ``` -These methods return an [`APIResponse`](https://github.com/everos/everos-python/tree/main/src/everos/_response.py) object. +These methods return an [`APIResponse`](https://github.com/evermemos/everos-python/tree/main/src/everos/_response.py) object. -The async client returns an [`AsyncAPIResponse`](https://github.com/everos/everos-python/tree/main/src/everos/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. +The async client returns an [`AsyncAPIResponse`](https://github.com/evermemos/everos-python/tree/main/src/everos/_response.py) with the same structure, the only difference being `await`able methods for reading the response content. #### `.with_streaming_response` @@ -427,7 +427,7 @@ This package generally follows [SemVer](https://semver.org/spec/v2.0.0.html) con We take backwards-compatibility seriously and work hard to ensure you can rely on a smooth upgrade experience. -We are keen for your feedback; please open an [issue](https://www.github.com/everos/everos-python/issues) with questions, bugs, or suggestions. +We are keen for your feedback; please open an [issue](https://www.github.com/evermemos/everos-python/issues) with questions, bugs, or suggestions. ### Determining the installed version diff --git a/bin/check-release-environment b/bin/check-release-environment index b845b0f..1e951e9 100644 --- a/bin/check-release-environment +++ b/bin/check-release-environment @@ -2,10 +2,6 @@ errors=() -if [ -z "${PYPI_TOKEN}" ]; then - errors+=("The PYPI_TOKEN secret has not been set. Please set it in either this repository's secrets or your organization secrets.") -fi - lenErrors=${#errors[@]} if [[ lenErrors -gt 0 ]]; then diff --git a/bin/publish-pypi b/bin/publish-pypi index e72ca2f..5895700 100644 --- a/bin/publish-pypi +++ b/bin/publish-pypi @@ -4,4 +4,8 @@ set -eux rm -rf dist mkdir -p dist uv build -uv publish --token=$PYPI_TOKEN +if [ -n "${PYPI_TOKEN:-}" ]; then + uv publish --token=$PYPI_TOKEN +else + uv publish +fi diff --git a/pyproject.toml b/pyproject.toml index 58c69fa..6ae998e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,8 +37,8 @@ classifiers = [ ] [project.urls] -Homepage = "https://github.com/everos/everos-python" -Repository = "https://github.com/everos/everos-python" +Homepage = "https://github.com/evermemos/everos-python" +Repository = "https://github.com/evermemos/everos-python" [project.optional-dependencies] aiohttp = ["aiohttp", "httpx_aiohttp>=0.1.9"] @@ -112,7 +112,7 @@ path = "README.md" [[tool.hatch.metadata.hooks.fancy-pypi-readme.substitutions]] # replace relative links with absolute links pattern = '\[(.+?)\]\(((?!https?://)\S+?)\)' -replacement = '[\1](https://github.com/everos/everos-python/tree/main/\g<2>)' +replacement = '[\1](https://github.com/evermemos/everos-python/tree/main/\g<2>)' [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/src/everos/resources/v1/groups.py b/src/everos/resources/v1/groups.py index 37b9e96..a4eb134 100644 --- a/src/everos/resources/v1/groups.py +++ b/src/everos/resources/v1/groups.py @@ -32,7 +32,7 @@ def with_raw_response(self) -> GroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return GroupsResourceWithRawResponse(self) @@ -41,7 +41,7 @@ def with_streaming_response(self) -> GroupsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return GroupsResourceWithStreamingResponse(self) @@ -183,7 +183,7 @@ def with_raw_response(self) -> AsyncGroupsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncGroupsResourceWithRawResponse(self) @@ -192,7 +192,7 @@ def with_streaming_response(self) -> AsyncGroupsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncGroupsResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/memories/agent.py b/src/everos/resources/v1/memories/agent.py index db0b068..5790b34 100644 --- a/src/everos/resources/v1/memories/agent.py +++ b/src/everos/resources/v1/memories/agent.py @@ -34,7 +34,7 @@ def with_raw_response(self) -> AgentResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AgentResourceWithRawResponse(self) @@ -43,7 +43,7 @@ def with_streaming_response(self) -> AgentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AgentResourceWithStreamingResponse(self) @@ -155,7 +155,7 @@ def with_raw_response(self) -> AsyncAgentResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncAgentResourceWithRawResponse(self) @@ -164,7 +164,7 @@ def with_streaming_response(self) -> AsyncAgentResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncAgentResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/memories/group.py b/src/everos/resources/v1/memories/group.py index 7492827..5350d16 100644 --- a/src/everos/resources/v1/memories/group.py +++ b/src/everos/resources/v1/memories/group.py @@ -34,7 +34,7 @@ def with_raw_response(self) -> GroupResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return GroupResourceWithRawResponse(self) @@ -43,7 +43,7 @@ def with_streaming_response(self) -> GroupResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return GroupResourceWithStreamingResponse(self) @@ -145,7 +145,7 @@ def with_raw_response(self) -> AsyncGroupResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncGroupResourceWithRawResponse(self) @@ -154,7 +154,7 @@ def with_streaming_response(self) -> AsyncGroupResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncGroupResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/memories/memories.py b/src/everos/resources/v1/memories/memories.py index a362fd5..764a85c 100644 --- a/src/everos/resources/v1/memories/memories.py +++ b/src/everos/resources/v1/memories/memories.py @@ -69,7 +69,7 @@ def with_raw_response(self) -> MemoriesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return MemoriesResourceWithRawResponse(self) @@ -78,7 +78,7 @@ def with_streaming_response(self) -> MemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return MemoriesResourceWithStreamingResponse(self) @@ -436,7 +436,7 @@ def with_raw_response(self) -> AsyncMemoriesResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncMemoriesResourceWithRawResponse(self) @@ -445,7 +445,7 @@ def with_streaming_response(self) -> AsyncMemoriesResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncMemoriesResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/object.py b/src/everos/resources/v1/object.py index a665968..0c81c2e 100644 --- a/src/everos/resources/v1/object.py +++ b/src/everos/resources/v1/object.py @@ -33,7 +33,7 @@ def with_raw_response(self) -> ObjectResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return ObjectResourceWithRawResponse(self) @@ -42,7 +42,7 @@ def with_streaming_response(self) -> ObjectResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return ObjectResourceWithStreamingResponse(self) @@ -97,7 +97,7 @@ def with_raw_response(self) -> AsyncObjectResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncObjectResourceWithRawResponse(self) @@ -106,7 +106,7 @@ def with_streaming_response(self) -> AsyncObjectResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncObjectResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/senders.py b/src/everos/resources/v1/senders.py index 74bc47c..96f260c 100644 --- a/src/everos/resources/v1/senders.py +++ b/src/everos/resources/v1/senders.py @@ -32,7 +32,7 @@ def with_raw_response(self) -> SendersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return SendersResourceWithRawResponse(self) @@ -41,7 +41,7 @@ def with_streaming_response(self) -> SendersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return SendersResourceWithStreamingResponse(self) @@ -168,7 +168,7 @@ def with_raw_response(self) -> AsyncSendersResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncSendersResourceWithRawResponse(self) @@ -177,7 +177,7 @@ def with_streaming_response(self) -> AsyncSendersResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncSendersResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/settings.py b/src/everos/resources/v1/settings.py index 46ef20e..4ca55e1 100644 --- a/src/everos/resources/v1/settings.py +++ b/src/everos/resources/v1/settings.py @@ -34,7 +34,7 @@ def with_raw_response(self) -> SettingsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return SettingsResourceWithRawResponse(self) @@ -43,7 +43,7 @@ def with_streaming_response(self) -> SettingsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return SettingsResourceWithStreamingResponse(self) @@ -133,7 +133,7 @@ def with_raw_response(self) -> AsyncSettingsResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncSettingsResourceWithRawResponse(self) @@ -142,7 +142,7 @@ def with_streaming_response(self) -> AsyncSettingsResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncSettingsResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/tasks.py b/src/everos/resources/v1/tasks.py index 9d8af3d..8ae9b6c 100644 --- a/src/everos/resources/v1/tasks.py +++ b/src/everos/resources/v1/tasks.py @@ -29,7 +29,7 @@ def with_raw_response(self) -> TasksResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return TasksResourceWithRawResponse(self) @@ -38,7 +38,7 @@ def with_streaming_response(self) -> TasksResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return TasksResourceWithStreamingResponse(self) @@ -88,7 +88,7 @@ def with_raw_response(self) -> AsyncTasksResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncTasksResourceWithRawResponse(self) @@ -97,7 +97,7 @@ def with_streaming_response(self) -> AsyncTasksResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncTasksResourceWithStreamingResponse(self) diff --git a/src/everos/resources/v1/v1.py b/src/everos/resources/v1/v1.py index 84ee3e4..5ef3595 100644 --- a/src/everos/resources/v1/v1.py +++ b/src/everos/resources/v1/v1.py @@ -93,7 +93,7 @@ def with_raw_response(self) -> V1ResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return V1ResourceWithRawResponse(self) @@ -102,7 +102,7 @@ def with_streaming_response(self) -> V1ResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return V1ResourceWithStreamingResponse(self) @@ -144,7 +144,7 @@ def with_raw_response(self) -> AsyncV1ResourceWithRawResponse: This property can be used as a prefix for any HTTP method call to return the raw response object instead of the parsed content. - For more information, see https://www.github.com/everos/everos-python#accessing-raw-response-data-eg-headers + For more information, see https://www.github.com/evermemos/everos-python#accessing-raw-response-data-eg-headers """ return AsyncV1ResourceWithRawResponse(self) @@ -153,7 +153,7 @@ def with_streaming_response(self) -> AsyncV1ResourceWithStreamingResponse: """ An alternative to `.with_raw_response` that doesn't eagerly read the response body. - For more information, see https://www.github.com/everos/everos-python#with_streaming_response + For more information, see https://www.github.com/evermemos/everos-python#with_streaming_response """ return AsyncV1ResourceWithStreamingResponse(self)