From 1ea16b448c46545292a754ee6819f503282dfabe Mon Sep 17 00:00:00 2001
From: "stainless-app[bot]"
<142633134+stainless-app[bot]@users.noreply.github.com>
Date: Fri, 28 Jun 2024 17:49:23 +0000
Subject: [PATCH] feat(api): add support for graphs and files endpoints (#15)
---
.gitignore | 1 +
.stats.yml | 4 +-
README.md | 2 +-
api.md | 40 ++
bin/publish-pypi | 3 +
pyproject.toml | 15 +
requirements-dev.lock | 3 +-
requirements.lock | 3 +-
src/writerai/_base_client.py | 25 +-
src/writerai/_client.py | 16 +
src/writerai/_utils/__init__.py | 4 +
src/writerai/_utils/_reflection.py | 42 ++
src/writerai/_utils/_sync.py | 19 +-
src/writerai/pagination.py | 85 +++
src/writerai/resources/__init__.py | 28 +
src/writerai/resources/files.py | 527 ++++++++++++++
src/writerai/resources/graphs.py | 672 ++++++++++++++++++
src/writerai/types/__init__.py | 13 +
src/writerai/types/file.py | 18 +
src/writerai/types/file_delete_response.py | 13 +
src/writerai/types/file_list_params.py | 19 +
src/writerai/types/file_upload_params.py | 20 +
src/writerai/types/graph.py | 30 +
.../types/graph_add_file_to_graph_params.py | 11 +
src/writerai/types/graph_create_params.py | 13 +
src/writerai/types/graph_create_response.py | 18 +
src/writerai/types/graph_delete_response.py | 13 +
src/writerai/types/graph_list_params.py | 17 +
.../graph_remove_file_from_graph_response.py | 13 +
src/writerai/types/graph_update_params.py | 13 +
src/writerai/types/graph_update_response.py | 18 +
src/writerai/types/model_list_response.py | 2 +-
tests/api_resources/test_files.py | 449 ++++++++++++
tests/api_resources/test_graphs.py | 612 ++++++++++++++++
34 files changed, 2769 insertions(+), 12 deletions(-)
create mode 100644 src/writerai/_utils/_reflection.py
create mode 100644 src/writerai/pagination.py
create mode 100644 src/writerai/resources/files.py
create mode 100644 src/writerai/resources/graphs.py
create mode 100644 src/writerai/types/file.py
create mode 100644 src/writerai/types/file_delete_response.py
create mode 100644 src/writerai/types/file_list_params.py
create mode 100644 src/writerai/types/file_upload_params.py
create mode 100644 src/writerai/types/graph.py
create mode 100644 src/writerai/types/graph_add_file_to_graph_params.py
create mode 100644 src/writerai/types/graph_create_params.py
create mode 100644 src/writerai/types/graph_create_response.py
create mode 100644 src/writerai/types/graph_delete_response.py
create mode 100644 src/writerai/types/graph_list_params.py
create mode 100644 src/writerai/types/graph_remove_file_from_graph_response.py
create mode 100644 src/writerai/types/graph_update_params.py
create mode 100644 src/writerai/types/graph_update_response.py
create mode 100644 tests/api_resources/test_files.py
create mode 100644 tests/api_resources/test_graphs.py
diff --git a/.gitignore b/.gitignore
index 0f9a66a..8779740 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+.prism.log
.vscode
_dev
diff --git a/.stats.yml b/.stats.yml
index f53008d..e396c3c 100644
--- a/.stats.yml
+++ b/.stats.yml
@@ -1,2 +1,2 @@
-configured_endpoints: 3
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-1bf320b94d765777875e8999e58068f684f81c4657bc25bf0d321b6adce778e1.yml
+configured_endpoints: 15
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/writerai%2Fwriter-e54b835d1d64d37343017f4919c60998496a1b2b5c8e0c67af82093efc613f2f.yml
diff --git a/README.md b/README.md
index 73110b8..03ab73f 100644
--- a/README.md
+++ b/README.md
@@ -352,7 +352,7 @@ You can directly override the [httpx client](https://www.python-httpx.org/api/#c
- Support for proxies
- Custom transports
-- Additional [advanced](https://www.python-httpx.org/advanced/#client-instances) functionality
+- Additional [advanced](https://www.python-httpx.org/advanced/clients/) functionality
```python
from writerai import Writer, DefaultHttpxClient
diff --git a/api.md b/api.md
index 0c42b4d..9a27ad9 100644
--- a/api.md
+++ b/api.md
@@ -33,3 +33,43 @@ from writerai.types import ModelListResponse
Methods:
- client.models.list() -> ModelListResponse
+
+# Graphs
+
+Types:
+
+```python
+from writerai.types import (
+ Graph,
+ GraphCreateResponse,
+ GraphUpdateResponse,
+ GraphDeleteResponse,
+ GraphRemoveFileFromGraphResponse,
+)
+```
+
+Methods:
+
+- client.graphs.create(\*\*params) -> GraphCreateResponse
+- client.graphs.retrieve(graph_id) -> Graph
+- client.graphs.update(graph_id, \*\*params) -> GraphUpdateResponse
+- client.graphs.list(\*\*params) -> SyncCursorPage[Graph]
+- client.graphs.delete(graph_id) -> GraphDeleteResponse
+- client.graphs.add_file_to_graph(graph_id, \*\*params) -> File
+- client.graphs.remove_file_from_graph(file_id, \*, graph_id) -> GraphRemoveFileFromGraphResponse
+
+# Files
+
+Types:
+
+```python
+from writerai.types import File, FileDeleteResponse
+```
+
+Methods:
+
+- client.files.retrieve(file_id) -> File
+- client.files.list(\*\*params) -> SyncCursorPage[File]
+- client.files.delete(file_id) -> FileDeleteResponse
+- client.files.download(file_id) -> BinaryAPIResponse
+- client.files.upload(\*\*params) -> File
diff --git a/bin/publish-pypi b/bin/publish-pypi
index 826054e..05bfccb 100644
--- a/bin/publish-pypi
+++ b/bin/publish-pypi
@@ -3,4 +3,7 @@
set -eux
mkdir -p dist
rye build --clean
+# Patching importlib-metadata version until upstream library version is updated
+# https://github.com/pypa/twine/issues/977#issuecomment-2189800841
+"$HOME/.rye/self/bin/python3" -m pip install 'importlib-metadata==7.2.1'
rye publish --yes --token=$PYPI_TOKEN
diff --git a/pyproject.toml b/pyproject.toml
index 9f88960..6d2d20a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -99,6 +99,21 @@ include = [
[tool.hatch.build.targets.wheel]
packages = ["src/writerai"]
+[tool.hatch.build.targets.sdist]
+# Basically everything except hidden files/directories (such as .github, .devcontainers, .python-version, etc)
+include = [
+ "/*.toml",
+ "/*.json",
+ "/*.lock",
+ "/*.md",
+ "/mypy.ini",
+ "/noxfile.py",
+ "bin/*",
+ "examples/*",
+ "src/*",
+ "tests/*",
+]
+
[tool.hatch.metadata.hooks.fancy-pypi-readme]
content-type = "text/markdown"
diff --git a/requirements-dev.lock b/requirements-dev.lock
index 1b60f5e..70a53ed 100644
--- a/requirements-dev.lock
+++ b/requirements-dev.lock
@@ -10,7 +10,7 @@
-e file:.
annotated-types==0.6.0
# via pydantic
-anyio==4.1.0
+anyio==4.4.0
# via httpx
# via writer-sdk
argcomplete==3.1.2
@@ -86,6 +86,7 @@ tomli==2.0.1
# via mypy
# via pytest
typing-extensions==4.8.0
+ # via anyio
# via mypy
# via pydantic
# via pydantic-core
diff --git a/requirements.lock b/requirements.lock
index efb1c1b..bd53c62 100644
--- a/requirements.lock
+++ b/requirements.lock
@@ -10,7 +10,7 @@
-e file:.
annotated-types==0.6.0
# via pydantic
-anyio==4.1.0
+anyio==4.4.0
# via httpx
# via writer-sdk
certifi==2023.7.22
@@ -38,6 +38,7 @@ sniffio==1.3.0
# via httpx
# via writer-sdk
typing-extensions==4.8.0
+ # via anyio
# via pydantic
# via pydantic-core
# via writer-sdk
diff --git a/src/writerai/_base_client.py b/src/writerai/_base_client.py
index 633afbb..5665fda 100644
--- a/src/writerai/_base_client.py
+++ b/src/writerai/_base_client.py
@@ -60,7 +60,7 @@
RequestOptions,
ModelBuilderProtocol,
)
-from ._utils import is_dict, is_list, is_given, lru_cache, is_mapping
+from ._utils import is_dict, is_list, asyncify, is_given, lru_cache, is_mapping
from ._compat import model_copy, model_dump
from ._models import GenericModel, FinalRequestOptions, validate_type, construct_type
from ._response import (
@@ -358,6 +358,7 @@ def __init__(
self._custom_query = custom_query or {}
self._strict_response_validation = _strict_response_validation
self._idempotency_header = None
+ self._platform: Platform | None = None
if max_retries is None: # pyright: ignore[reportUnnecessaryComparison]
raise TypeError(
@@ -456,7 +457,7 @@ def _build_request(
raise RuntimeError(f"Unexpected JSON data type, {type(json_data)}, cannot merge with `extra_body`")
headers = self._build_headers(options)
- params = _merge_mappings(self._custom_query, options.params)
+ params = _merge_mappings(self.default_query, options.params)
content_type = headers.get("Content-Type")
# If the given Content-Type header is multipart/form-data then it
@@ -592,6 +593,12 @@ def default_headers(self) -> dict[str, str | Omit]:
**self._custom_headers,
}
+ @property
+ def default_query(self) -> dict[str, object]:
+ return {
+ **self._custom_query,
+ }
+
def _validate_headers(
self,
headers: Headers, # noqa: ARG002
@@ -616,7 +623,10 @@ def base_url(self, url: URL | str) -> None:
self._base_url = self._enforce_trailing_slash(url if isinstance(url, URL) else URL(url))
def platform_headers(self) -> Dict[str, str]:
- return platform_headers(self._version)
+ # the actual implementation is in a separate `lru_cache` decorated
+ # function because adding `lru_cache` to methods will leak memory
+ # https://github.com/python/cpython/issues/88476
+ return platform_headers(self._version, platform=self._platform)
def _parse_retry_after_header(self, response_headers: Optional[httpx.Headers] = None) -> float | None:
"""Returns a float of the number of seconds (not milliseconds) to wait after retrying, or None if unspecified.
@@ -1492,6 +1502,11 @@ async def _request(
stream_cls: type[_AsyncStreamT] | None,
remaining_retries: int | None,
) -> ResponseT | _AsyncStreamT:
+ if self._platform is None:
+ # `get_platform` can make blocking IO calls so we
+ # execute it earlier while we are in an async context
+ self._platform = await asyncify(get_platform)()
+
cast_to = self._maybe_override_cast_to(cast_to, options)
await self._prepare_options(options)
@@ -1915,11 +1930,11 @@ def get_platform() -> Platform:
@lru_cache(maxsize=None)
-def platform_headers(version: str) -> Dict[str, str]:
+def platform_headers(version: str, *, platform: Platform | None) -> Dict[str, str]:
return {
"X-Stainless-Lang": "python",
"X-Stainless-Package-Version": version,
- "X-Stainless-OS": str(get_platform()),
+ "X-Stainless-OS": str(platform or get_platform()),
"X-Stainless-Arch": str(get_architecture()),
"X-Stainless-Runtime": get_python_runtime(),
"X-Stainless-Runtime-Version": get_python_version(),
diff --git a/src/writerai/_client.py b/src/writerai/_client.py
index 97dc25b..ab24307 100644
--- a/src/writerai/_client.py
+++ b/src/writerai/_client.py
@@ -49,6 +49,8 @@ class Writer(SyncAPIClient):
chat: resources.ChatResource
completions: resources.CompletionsResource
models: resources.ModelsResource
+ graphs: resources.GraphsResource
+ files: resources.FilesResource
with_raw_response: WriterWithRawResponse
with_streaming_response: WriterWithStreamedResponse
@@ -111,6 +113,8 @@ def __init__(
self.chat = resources.ChatResource(self)
self.completions = resources.CompletionsResource(self)
self.models = resources.ModelsResource(self)
+ self.graphs = resources.GraphsResource(self)
+ self.files = resources.FilesResource(self)
self.with_raw_response = WriterWithRawResponse(self)
self.with_streaming_response = WriterWithStreamedResponse(self)
@@ -223,6 +227,8 @@ class AsyncWriter(AsyncAPIClient):
chat: resources.AsyncChatResource
completions: resources.AsyncCompletionsResource
models: resources.AsyncModelsResource
+ graphs: resources.AsyncGraphsResource
+ files: resources.AsyncFilesResource
with_raw_response: AsyncWriterWithRawResponse
with_streaming_response: AsyncWriterWithStreamedResponse
@@ -285,6 +291,8 @@ def __init__(
self.chat = resources.AsyncChatResource(self)
self.completions = resources.AsyncCompletionsResource(self)
self.models = resources.AsyncModelsResource(self)
+ self.graphs = resources.AsyncGraphsResource(self)
+ self.files = resources.AsyncFilesResource(self)
self.with_raw_response = AsyncWriterWithRawResponse(self)
self.with_streaming_response = AsyncWriterWithStreamedResponse(self)
@@ -398,6 +406,8 @@ def __init__(self, client: Writer) -> None:
self.chat = resources.ChatResourceWithRawResponse(client.chat)
self.completions = resources.CompletionsResourceWithRawResponse(client.completions)
self.models = resources.ModelsResourceWithRawResponse(client.models)
+ self.graphs = resources.GraphsResourceWithRawResponse(client.graphs)
+ self.files = resources.FilesResourceWithRawResponse(client.files)
class AsyncWriterWithRawResponse:
@@ -405,6 +415,8 @@ def __init__(self, client: AsyncWriter) -> None:
self.chat = resources.AsyncChatResourceWithRawResponse(client.chat)
self.completions = resources.AsyncCompletionsResourceWithRawResponse(client.completions)
self.models = resources.AsyncModelsResourceWithRawResponse(client.models)
+ self.graphs = resources.AsyncGraphsResourceWithRawResponse(client.graphs)
+ self.files = resources.AsyncFilesResourceWithRawResponse(client.files)
class WriterWithStreamedResponse:
@@ -412,6 +424,8 @@ def __init__(self, client: Writer) -> None:
self.chat = resources.ChatResourceWithStreamingResponse(client.chat)
self.completions = resources.CompletionsResourceWithStreamingResponse(client.completions)
self.models = resources.ModelsResourceWithStreamingResponse(client.models)
+ self.graphs = resources.GraphsResourceWithStreamingResponse(client.graphs)
+ self.files = resources.FilesResourceWithStreamingResponse(client.files)
class AsyncWriterWithStreamedResponse:
@@ -419,6 +433,8 @@ def __init__(self, client: AsyncWriter) -> None:
self.chat = resources.AsyncChatResourceWithStreamingResponse(client.chat)
self.completions = resources.AsyncCompletionsResourceWithStreamingResponse(client.completions)
self.models = resources.AsyncModelsResourceWithStreamingResponse(client.models)
+ self.graphs = resources.AsyncGraphsResourceWithStreamingResponse(client.graphs)
+ self.files = resources.AsyncFilesResourceWithStreamingResponse(client.files)
Client = Writer
diff --git a/src/writerai/_utils/__init__.py b/src/writerai/_utils/__init__.py
index 31b5b22..3efe66c 100644
--- a/src/writerai/_utils/__init__.py
+++ b/src/writerai/_utils/__init__.py
@@ -49,3 +49,7 @@
maybe_transform as maybe_transform,
async_maybe_transform as async_maybe_transform,
)
+from ._reflection import (
+ function_has_argument as function_has_argument,
+ assert_signatures_in_sync as assert_signatures_in_sync,
+)
diff --git a/src/writerai/_utils/_reflection.py b/src/writerai/_utils/_reflection.py
new file mode 100644
index 0000000..9a53c7b
--- /dev/null
+++ b/src/writerai/_utils/_reflection.py
@@ -0,0 +1,42 @@
+from __future__ import annotations
+
+import inspect
+from typing import Any, Callable
+
+
+def function_has_argument(func: Callable[..., Any], arg_name: str) -> bool:
+ """Returns whether or not the given function has a specific parameter"""
+ sig = inspect.signature(func)
+ return arg_name in sig.parameters
+
+
+def assert_signatures_in_sync(
+ source_func: Callable[..., Any],
+ check_func: Callable[..., Any],
+ *,
+ exclude_params: set[str] = set(),
+) -> None:
+ """Ensure that the signature of the second function matches the first."""
+
+ check_sig = inspect.signature(check_func)
+ source_sig = inspect.signature(source_func)
+
+ errors: list[str] = []
+
+ for name, source_param in source_sig.parameters.items():
+ if name in exclude_params:
+ continue
+
+ custom_param = check_sig.parameters.get(name)
+ if not custom_param:
+ errors.append(f"the `{name}` param is missing")
+ continue
+
+ if custom_param.annotation != source_param.annotation:
+ errors.append(
+ f"types for the `{name}` param are do not match; source={repr(source_param.annotation)} checking={repr(source_param.annotation)}"
+ )
+ continue
+
+ if errors:
+ raise AssertionError(f"{len(errors)} errors encountered when comparing signatures:\n\n" + "\n\n".join(errors))
diff --git a/src/writerai/_utils/_sync.py b/src/writerai/_utils/_sync.py
index 595924e..d0d8103 100644
--- a/src/writerai/_utils/_sync.py
+++ b/src/writerai/_utils/_sync.py
@@ -7,6 +7,8 @@
import anyio
import anyio.to_thread
+from ._reflection import function_has_argument
+
T_Retval = TypeVar("T_Retval")
T_ParamSpec = ParamSpec("T_ParamSpec")
@@ -59,6 +61,21 @@ def do_work(arg1, arg2, kwarg1="", kwarg2="") -> str:
async def wrapper(*args: T_ParamSpec.args, **kwargs: T_ParamSpec.kwargs) -> T_Retval:
partial_f = functools.partial(function, *args, **kwargs)
- return await anyio.to_thread.run_sync(partial_f, cancellable=cancellable, limiter=limiter)
+
+ # In `v4.1.0` anyio added the `abandon_on_cancel` argument and deprecated the old
+ # `cancellable` argument, so we need to use the new `abandon_on_cancel` to avoid
+ # surfacing deprecation warnings.
+ if function_has_argument(anyio.to_thread.run_sync, "abandon_on_cancel"):
+ return await anyio.to_thread.run_sync(
+ partial_f,
+ abandon_on_cancel=cancellable,
+ limiter=limiter,
+ )
+
+ return await anyio.to_thread.run_sync(
+ partial_f,
+ cancellable=cancellable,
+ limiter=limiter,
+ )
return wrapper
diff --git a/src/writerai/pagination.py b/src/writerai/pagination.py
new file mode 100644
index 0000000..16ba687
--- /dev/null
+++ b/src/writerai/pagination.py
@@ -0,0 +1,85 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import Any, List, Generic, TypeVar, Optional, cast
+from typing_extensions import Protocol, override, runtime_checkable
+
+from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage
+
+__all__ = ["SyncCursorPage", "AsyncCursorPage"]
+
+_T = TypeVar("_T")
+
+
+@runtime_checkable
+class CursorPageItem(Protocol):
+ id: Optional[str]
+
+
+class SyncCursorPage(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
+ data: List[_T]
+ has_more: bool
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ data = self.data
+ if not data:
+ return []
+ return data
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ is_forwards = not self._options.params.get("before", False)
+
+ data = self.data
+ if not data:
+ return None
+
+ if is_forwards:
+ item = cast(Any, data[-1])
+ if not isinstance(item, CursorPageItem) or item.id is None:
+ # TODO emit warning log
+ return None
+
+ return PageInfo(params={"after": item.id})
+ else:
+ item = cast(Any, self.data[0])
+ if not isinstance(item, CursorPageItem) or item.id is None:
+ # TODO emit warning log
+ return None
+
+ return PageInfo(params={"before": item.id})
+
+
+class AsyncCursorPage(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
+ data: List[_T]
+ has_more: bool
+
+ @override
+ def _get_page_items(self) -> List[_T]:
+ data = self.data
+ if not data:
+ return []
+ return data
+
+ @override
+ def next_page_info(self) -> Optional[PageInfo]:
+ is_forwards = not self._options.params.get("before", False)
+
+ data = self.data
+ if not data:
+ return None
+
+ if is_forwards:
+ item = cast(Any, data[-1])
+ if not isinstance(item, CursorPageItem) or item.id is None:
+ # TODO emit warning log
+ return None
+
+ return PageInfo(params={"after": item.id})
+ else:
+ item = cast(Any, self.data[0])
+ if not isinstance(item, CursorPageItem) or item.id is None:
+ # TODO emit warning log
+ return None
+
+ return PageInfo(params={"before": item.id})
diff --git a/src/writerai/resources/__init__.py b/src/writerai/resources/__init__.py
index d4fe705..4d17d60 100644
--- a/src/writerai/resources/__init__.py
+++ b/src/writerai/resources/__init__.py
@@ -8,6 +8,22 @@
ChatResourceWithStreamingResponse,
AsyncChatResourceWithStreamingResponse,
)
+from .files import (
+ FilesResource,
+ AsyncFilesResource,
+ FilesResourceWithRawResponse,
+ AsyncFilesResourceWithRawResponse,
+ FilesResourceWithStreamingResponse,
+ AsyncFilesResourceWithStreamingResponse,
+)
+from .graphs import (
+ GraphsResource,
+ AsyncGraphsResource,
+ GraphsResourceWithRawResponse,
+ AsyncGraphsResourceWithRawResponse,
+ GraphsResourceWithStreamingResponse,
+ AsyncGraphsResourceWithStreamingResponse,
+)
from .models import (
ModelsResource,
AsyncModelsResource,
@@ -44,4 +60,16 @@
"AsyncModelsResourceWithRawResponse",
"ModelsResourceWithStreamingResponse",
"AsyncModelsResourceWithStreamingResponse",
+ "GraphsResource",
+ "AsyncGraphsResource",
+ "GraphsResourceWithRawResponse",
+ "AsyncGraphsResourceWithRawResponse",
+ "GraphsResourceWithStreamingResponse",
+ "AsyncGraphsResourceWithStreamingResponse",
+ "FilesResource",
+ "AsyncFilesResource",
+ "FilesResourceWithRawResponse",
+ "AsyncFilesResourceWithRawResponse",
+ "FilesResourceWithStreamingResponse",
+ "AsyncFilesResourceWithStreamingResponse",
]
diff --git a/src/writerai/resources/files.py b/src/writerai/resources/files.py
new file mode 100644
index 0000000..64fc842
--- /dev/null
+++ b/src/writerai/resources/files.py
@@ -0,0 +1,527 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import file_list_params, file_upload_params
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven, FileTypes
+from .._utils import (
+ maybe_transform,
+ async_maybe_transform,
+)
+from .._compat import cached_property
+from .._resource import SyncAPIResource, AsyncAPIResource
+from .._response import (
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+ to_raw_response_wrapper,
+ to_streamed_response_wrapper,
+ async_to_raw_response_wrapper,
+ to_custom_raw_response_wrapper,
+ async_to_streamed_response_wrapper,
+ to_custom_streamed_response_wrapper,
+ async_to_custom_raw_response_wrapper,
+ async_to_custom_streamed_response_wrapper,
+)
+from ..pagination import SyncCursorPage, AsyncCursorPage
+from ..types.file import File
+from .._base_client import (
+ AsyncPaginator,
+ make_request_options,
+)
+from ..types.file_delete_response import FileDeleteResponse
+
+__all__ = ["FilesResource", "AsyncFilesResource"]
+
+
+class FilesResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> FilesResourceWithRawResponse:
+ return FilesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> FilesResourceWithStreamingResponse:
+ return FilesResourceWithStreamingResponse(self)
+
+ def retrieve(
+ self,
+ file_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,
+ ) -> File:
+ """
+ Get metadata of a file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return self._get(
+ f"/v1/files/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+ def list(
+ self,
+ *,
+ after: str | NotGiven = NOT_GIVEN,
+ before: str | NotGiven = NOT_GIVEN,
+ graph_id: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> SyncCursorPage[File]:
+ """
+ Get metadata of all files
+
+ 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
+ """
+ return self._get_api_list(
+ "/v1/files",
+ page=SyncCursorPage[File],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "before": before,
+ "graph_id": graph_id,
+ "limit": limit,
+ "order": order,
+ },
+ file_list_params.FileListParams,
+ ),
+ ),
+ model=File,
+ )
+
+ def delete(
+ self,
+ file_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,
+ ) -> FileDeleteResponse:
+ """
+ Delete file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return self._delete(
+ f"/v1/files/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FileDeleteResponse,
+ )
+
+ def download(
+ self,
+ file_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,
+ ) -> BinaryAPIResponse:
+ """
+ Download a file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ return self._get(
+ f"/v1/files/{file_id}/download",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=BinaryAPIResponse,
+ )
+
+ def upload(
+ self,
+ *,
+ content: FileTypes,
+ content_disposition: str,
+ content_length: int,
+ content_type: 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,
+ ) -> File:
+ """
+ Upload file
+
+ 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
+ """
+ extra_headers = {
+ "Content-Disposition": content_disposition,
+ "Content-Length": str(content_length),
+ "Content-Type": content_type,
+ **(extra_headers or {}),
+ }
+ return self._post(
+ "/v1/files",
+ body=maybe_transform(content, file_upload_params.FileUploadParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+
+class AsyncFilesResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncFilesResourceWithRawResponse:
+ return AsyncFilesResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncFilesResourceWithStreamingResponse:
+ return AsyncFilesResourceWithStreamingResponse(self)
+
+ async def retrieve(
+ self,
+ file_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,
+ ) -> File:
+ """
+ Get metadata of a file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return await self._get(
+ f"/v1/files/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+ def list(
+ self,
+ *,
+ after: str | NotGiven = NOT_GIVEN,
+ before: str | NotGiven = NOT_GIVEN,
+ graph_id: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> AsyncPaginator[File, AsyncCursorPage[File]]:
+ """
+ Get metadata of all files
+
+ 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
+ """
+ return self._get_api_list(
+ "/v1/files",
+ page=AsyncCursorPage[File],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "before": before,
+ "graph_id": graph_id,
+ "limit": limit,
+ "order": order,
+ },
+ file_list_params.FileListParams,
+ ),
+ ),
+ model=File,
+ )
+
+ async def delete(
+ self,
+ file_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,
+ ) -> FileDeleteResponse:
+ """
+ Delete file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return await self._delete(
+ f"/v1/files/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=FileDeleteResponse,
+ )
+
+ async def download(
+ self,
+ file_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,
+ ) -> AsyncBinaryAPIResponse:
+ """
+ Download a file
+
+ 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 file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ extra_headers = {"Accept": "application/octet-stream", **(extra_headers or {})}
+ return await self._get(
+ f"/v1/files/{file_id}/download",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=AsyncBinaryAPIResponse,
+ )
+
+ async def upload(
+ self,
+ *,
+ content: FileTypes,
+ content_disposition: str,
+ content_length: int,
+ content_type: 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,
+ ) -> File:
+ """
+ Upload file
+
+ 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
+ """
+ extra_headers = {
+ "Content-Disposition": content_disposition,
+ "Content-Length": str(content_length),
+ "Content-Type": content_type,
+ **(extra_headers or {}),
+ }
+ return await self._post(
+ "/v1/files",
+ body=await async_maybe_transform(content, file_upload_params.FileUploadParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+
+class FilesResourceWithRawResponse:
+ def __init__(self, files: FilesResource) -> None:
+ self._files = files
+
+ self.retrieve = to_raw_response_wrapper(
+ files.retrieve,
+ )
+ self.list = to_raw_response_wrapper(
+ files.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ files.delete,
+ )
+ self.download = to_custom_raw_response_wrapper(
+ files.download,
+ BinaryAPIResponse,
+ )
+ self.upload = to_raw_response_wrapper(
+ files.upload,
+ )
+
+
+class AsyncFilesResourceWithRawResponse:
+ def __init__(self, files: AsyncFilesResource) -> None:
+ self._files = files
+
+ self.retrieve = async_to_raw_response_wrapper(
+ files.retrieve,
+ )
+ self.list = async_to_raw_response_wrapper(
+ files.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ files.delete,
+ )
+ self.download = async_to_custom_raw_response_wrapper(
+ files.download,
+ AsyncBinaryAPIResponse,
+ )
+ self.upload = async_to_raw_response_wrapper(
+ files.upload,
+ )
+
+
+class FilesResourceWithStreamingResponse:
+ def __init__(self, files: FilesResource) -> None:
+ self._files = files
+
+ self.retrieve = to_streamed_response_wrapper(
+ files.retrieve,
+ )
+ self.list = to_streamed_response_wrapper(
+ files.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ files.delete,
+ )
+ self.download = to_custom_streamed_response_wrapper(
+ files.download,
+ StreamedBinaryAPIResponse,
+ )
+ self.upload = to_streamed_response_wrapper(
+ files.upload,
+ )
+
+
+class AsyncFilesResourceWithStreamingResponse:
+ def __init__(self, files: AsyncFilesResource) -> None:
+ self._files = files
+
+ self.retrieve = async_to_streamed_response_wrapper(
+ files.retrieve,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ files.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ files.delete,
+ )
+ self.download = async_to_custom_streamed_response_wrapper(
+ files.download,
+ AsyncStreamedBinaryAPIResponse,
+ )
+ self.upload = async_to_streamed_response_wrapper(
+ files.upload,
+ )
diff --git a/src/writerai/resources/graphs.py b/src/writerai/resources/graphs.py
new file mode 100644
index 0000000..73c7add
--- /dev/null
+++ b/src/writerai/resources/graphs.py
@@ -0,0 +1,672 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal
+
+import httpx
+
+from ..types import (
+ graph_list_params,
+ graph_create_params,
+ graph_update_params,
+ graph_add_file_to_graph_params,
+)
+from .._types import NOT_GIVEN, Body, Query, Headers, NotGiven
+from .._utils import (
+ maybe_transform,
+ async_maybe_transform,
+)
+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 ..pagination import SyncCursorPage, AsyncCursorPage
+from ..types.file import File
+from ..types.graph import Graph
+from .._base_client import (
+ AsyncPaginator,
+ make_request_options,
+)
+from ..types.graph_create_response import GraphCreateResponse
+from ..types.graph_delete_response import GraphDeleteResponse
+from ..types.graph_update_response import GraphUpdateResponse
+from ..types.graph_remove_file_from_graph_response import GraphRemoveFileFromGraphResponse
+
+__all__ = ["GraphsResource", "AsyncGraphsResource"]
+
+
+class GraphsResource(SyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> GraphsResourceWithRawResponse:
+ return GraphsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> GraphsResourceWithStreamingResponse:
+ return GraphsResourceWithStreamingResponse(self)
+
+ def create(
+ self,
+ *,
+ name: str,
+ description: str | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> GraphCreateResponse:
+ """
+ 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
+ """
+ return self._post(
+ "/v1/graphs",
+ body=maybe_transform(
+ {
+ "name": name,
+ "description": description,
+ },
+ graph_create_params.GraphCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphCreateResponse,
+ )
+
+ def retrieve(
+ self,
+ graph_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,
+ ) -> Graph:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return self._get(
+ f"/v1/graphs/{graph_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Graph,
+ )
+
+ def update(
+ self,
+ graph_id: str,
+ *,
+ name: str,
+ description: str | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> GraphUpdateResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return self._put(
+ f"/v1/graphs/{graph_id}",
+ body=maybe_transform(
+ {
+ "name": name,
+ "description": description,
+ },
+ graph_update_params.GraphUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ after: str | NotGiven = NOT_GIVEN,
+ before: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> SyncCursorPage[Graph]:
+ """
+ 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
+ """
+ return self._get_api_list(
+ "/v1/graphs",
+ page=SyncCursorPage[Graph],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "before": before,
+ "limit": limit,
+ "order": order,
+ },
+ graph_list_params.GraphListParams,
+ ),
+ ),
+ model=Graph,
+ )
+
+ def delete(
+ self,
+ graph_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,
+ ) -> GraphDeleteResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return self._delete(
+ f"/v1/graphs/{graph_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphDeleteResponse,
+ )
+
+ def add_file_to_graph(
+ self,
+ graph_id: str,
+ *,
+ file_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,
+ ) -> File:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return self._post(
+ f"/v1/graphs/{graph_id}/file",
+ body=maybe_transform({"file_id": file_id}, graph_add_file_to_graph_params.GraphAddFileToGraphParams),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+ def remove_file_from_graph(
+ self,
+ file_id: str,
+ *,
+ graph_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,
+ ) -> GraphRemoveFileFromGraphResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ if not file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return self._delete(
+ f"/v1/graphs/{graph_id}/file/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphRemoveFileFromGraphResponse,
+ )
+
+
+class AsyncGraphsResource(AsyncAPIResource):
+ @cached_property
+ def with_raw_response(self) -> AsyncGraphsResourceWithRawResponse:
+ return AsyncGraphsResourceWithRawResponse(self)
+
+ @cached_property
+ def with_streaming_response(self) -> AsyncGraphsResourceWithStreamingResponse:
+ return AsyncGraphsResourceWithStreamingResponse(self)
+
+ async def create(
+ self,
+ *,
+ name: str,
+ description: str | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> GraphCreateResponse:
+ """
+ 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
+ """
+ return await self._post(
+ "/v1/graphs",
+ body=await async_maybe_transform(
+ {
+ "name": name,
+ "description": description,
+ },
+ graph_create_params.GraphCreateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphCreateResponse,
+ )
+
+ async def retrieve(
+ self,
+ graph_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,
+ ) -> Graph:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return await self._get(
+ f"/v1/graphs/{graph_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=Graph,
+ )
+
+ async def update(
+ self,
+ graph_id: str,
+ *,
+ name: str,
+ description: str | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> GraphUpdateResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return await self._put(
+ f"/v1/graphs/{graph_id}",
+ body=await async_maybe_transform(
+ {
+ "name": name,
+ "description": description,
+ },
+ graph_update_params.GraphUpdateParams,
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphUpdateResponse,
+ )
+
+ def list(
+ self,
+ *,
+ after: str | NotGiven = NOT_GIVEN,
+ before: str | NotGiven = NOT_GIVEN,
+ limit: int | NotGiven = NOT_GIVEN,
+ order: Literal["asc", "desc"] | NotGiven = NOT_GIVEN,
+ # 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,
+ ) -> AsyncPaginator[Graph, AsyncCursorPage[Graph]]:
+ """
+ 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
+ """
+ return self._get_api_list(
+ "/v1/graphs",
+ page=AsyncCursorPage[Graph],
+ options=make_request_options(
+ extra_headers=extra_headers,
+ extra_query=extra_query,
+ extra_body=extra_body,
+ timeout=timeout,
+ query=maybe_transform(
+ {
+ "after": after,
+ "before": before,
+ "limit": limit,
+ "order": order,
+ },
+ graph_list_params.GraphListParams,
+ ),
+ ),
+ model=Graph,
+ )
+
+ async def delete(
+ self,
+ graph_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,
+ ) -> GraphDeleteResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return await self._delete(
+ f"/v1/graphs/{graph_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphDeleteResponse,
+ )
+
+ async def add_file_to_graph(
+ self,
+ graph_id: str,
+ *,
+ file_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,
+ ) -> File:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ return await self._post(
+ f"/v1/graphs/{graph_id}/file",
+ body=await async_maybe_transform(
+ {"file_id": file_id}, graph_add_file_to_graph_params.GraphAddFileToGraphParams
+ ),
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=File,
+ )
+
+ async def remove_file_from_graph(
+ self,
+ file_id: str,
+ *,
+ graph_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,
+ ) -> GraphRemoveFileFromGraphResponse:
+ """
+ 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 graph_id:
+ raise ValueError(f"Expected a non-empty value for `graph_id` but received {graph_id!r}")
+ if not file_id:
+ raise ValueError(f"Expected a non-empty value for `file_id` but received {file_id!r}")
+ return await self._delete(
+ f"/v1/graphs/{graph_id}/file/{file_id}",
+ options=make_request_options(
+ extra_headers=extra_headers, extra_query=extra_query, extra_body=extra_body, timeout=timeout
+ ),
+ cast_to=GraphRemoveFileFromGraphResponse,
+ )
+
+
+class GraphsResourceWithRawResponse:
+ def __init__(self, graphs: GraphsResource) -> None:
+ self._graphs = graphs
+
+ self.create = to_raw_response_wrapper(
+ graphs.create,
+ )
+ self.retrieve = to_raw_response_wrapper(
+ graphs.retrieve,
+ )
+ self.update = to_raw_response_wrapper(
+ graphs.update,
+ )
+ self.list = to_raw_response_wrapper(
+ graphs.list,
+ )
+ self.delete = to_raw_response_wrapper(
+ graphs.delete,
+ )
+ self.add_file_to_graph = to_raw_response_wrapper(
+ graphs.add_file_to_graph,
+ )
+ self.remove_file_from_graph = to_raw_response_wrapper(
+ graphs.remove_file_from_graph,
+ )
+
+
+class AsyncGraphsResourceWithRawResponse:
+ def __init__(self, graphs: AsyncGraphsResource) -> None:
+ self._graphs = graphs
+
+ self.create = async_to_raw_response_wrapper(
+ graphs.create,
+ )
+ self.retrieve = async_to_raw_response_wrapper(
+ graphs.retrieve,
+ )
+ self.update = async_to_raw_response_wrapper(
+ graphs.update,
+ )
+ self.list = async_to_raw_response_wrapper(
+ graphs.list,
+ )
+ self.delete = async_to_raw_response_wrapper(
+ graphs.delete,
+ )
+ self.add_file_to_graph = async_to_raw_response_wrapper(
+ graphs.add_file_to_graph,
+ )
+ self.remove_file_from_graph = async_to_raw_response_wrapper(
+ graphs.remove_file_from_graph,
+ )
+
+
+class GraphsResourceWithStreamingResponse:
+ def __init__(self, graphs: GraphsResource) -> None:
+ self._graphs = graphs
+
+ self.create = to_streamed_response_wrapper(
+ graphs.create,
+ )
+ self.retrieve = to_streamed_response_wrapper(
+ graphs.retrieve,
+ )
+ self.update = to_streamed_response_wrapper(
+ graphs.update,
+ )
+ self.list = to_streamed_response_wrapper(
+ graphs.list,
+ )
+ self.delete = to_streamed_response_wrapper(
+ graphs.delete,
+ )
+ self.add_file_to_graph = to_streamed_response_wrapper(
+ graphs.add_file_to_graph,
+ )
+ self.remove_file_from_graph = to_streamed_response_wrapper(
+ graphs.remove_file_from_graph,
+ )
+
+
+class AsyncGraphsResourceWithStreamingResponse:
+ def __init__(self, graphs: AsyncGraphsResource) -> None:
+ self._graphs = graphs
+
+ self.create = async_to_streamed_response_wrapper(
+ graphs.create,
+ )
+ self.retrieve = async_to_streamed_response_wrapper(
+ graphs.retrieve,
+ )
+ self.update = async_to_streamed_response_wrapper(
+ graphs.update,
+ )
+ self.list = async_to_streamed_response_wrapper(
+ graphs.list,
+ )
+ self.delete = async_to_streamed_response_wrapper(
+ graphs.delete,
+ )
+ self.add_file_to_graph = async_to_streamed_response_wrapper(
+ graphs.add_file_to_graph,
+ )
+ self.remove_file_from_graph = async_to_streamed_response_wrapper(
+ graphs.remove_file_from_graph,
+ )
diff --git a/src/writerai/types/__init__.py b/src/writerai/types/__init__.py
index d04f7bf..0e54913 100644
--- a/src/writerai/types/__init__.py
+++ b/src/writerai/types/__init__.py
@@ -3,9 +3,22 @@
from __future__ import annotations
from .chat import Chat as Chat
+from .file import File as File
+from .graph import Graph as Graph
from .completion import Completion as Completion
from .streaming_data import StreamingData as StreamingData
from .chat_chat_params import ChatChatParams as ChatChatParams
+from .file_list_params import FileListParams as FileListParams
+from .graph_list_params import GraphListParams as GraphListParams
+from .file_upload_params import FileUploadParams as FileUploadParams
from .chat_streaming_data import ChatStreamingData as ChatStreamingData
+from .graph_create_params import GraphCreateParams as GraphCreateParams
+from .graph_update_params import GraphUpdateParams as GraphUpdateParams
from .model_list_response import ModelListResponse as ModelListResponse
+from .file_delete_response import FileDeleteResponse as FileDeleteResponse
+from .graph_create_response import GraphCreateResponse as GraphCreateResponse
+from .graph_delete_response import GraphDeleteResponse as GraphDeleteResponse
+from .graph_update_response import GraphUpdateResponse as GraphUpdateResponse
from .completion_create_params import CompletionCreateParams as CompletionCreateParams
+from .graph_add_file_to_graph_params import GraphAddFileToGraphParams as GraphAddFileToGraphParams
+from .graph_remove_file_from_graph_response import GraphRemoveFileFromGraphResponse as GraphRemoveFileFromGraphResponse
diff --git a/src/writerai/types/file.py b/src/writerai/types/file.py
new file mode 100644
index 0000000..9448c40
--- /dev/null
+++ b/src/writerai/types/file.py
@@ -0,0 +1,18 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from typing import List
+from datetime import datetime
+
+from .._models import BaseModel
+
+__all__ = ["File"]
+
+
+class File(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ graph_ids: List[str]
+
+ name: str
diff --git a/src/writerai/types/file_delete_response.py b/src/writerai/types/file_delete_response.py
new file mode 100644
index 0000000..450d558
--- /dev/null
+++ b/src/writerai/types/file_delete_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+
+from .._models import BaseModel
+
+__all__ = ["FileDeleteResponse"]
+
+
+class FileDeleteResponse(BaseModel):
+ id: str
+
+ deleted: bool
diff --git a/src/writerai/types/file_list_params.py b/src/writerai/types/file_list_params.py
new file mode 100644
index 0000000..6086695
--- /dev/null
+++ b/src/writerai/types/file_list_params.py
@@ -0,0 +1,19 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["FileListParams"]
+
+
+class FileListParams(TypedDict, total=False):
+ after: str
+
+ before: str
+
+ graph_id: str
+
+ limit: int
+
+ order: Literal["asc", "desc"]
diff --git a/src/writerai/types/file_upload_params.py b/src/writerai/types/file_upload_params.py
new file mode 100644
index 0000000..4a47b72
--- /dev/null
+++ b/src/writerai/types/file_upload_params.py
@@ -0,0 +1,20 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, Annotated, TypedDict
+
+from .._types import FileTypes
+from .._utils import PropertyInfo
+
+__all__ = ["FileUploadParams"]
+
+
+class FileUploadParams(TypedDict, total=False):
+ content: Required[FileTypes]
+
+ content_disposition: Required[Annotated[str, PropertyInfo(alias="Content-Disposition")]]
+
+ content_length: Required[Annotated[int, PropertyInfo(alias="Content-Length")]]
+
+ content_type: Required[Annotated[str, PropertyInfo(alias="Content-Type")]]
diff --git a/src/writerai/types/graph.py b/src/writerai/types/graph.py
new file mode 100644
index 0000000..2045a5d
--- /dev/null
+++ b/src/writerai/types/graph.py
@@ -0,0 +1,30 @@
+# 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__ = ["Graph", "FileStatus"]
+
+
+class FileStatus(BaseModel):
+ completed: int
+
+ failed: int
+
+ in_progress: int
+
+ total: int
+
+
+class Graph(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ file_status: FileStatus
+
+ name: str
+
+ description: Optional[str] = None
diff --git a/src/writerai/types/graph_add_file_to_graph_params.py b/src/writerai/types/graph_add_file_to_graph_params.py
new file mode 100644
index 0000000..b1bab49
--- /dev/null
+++ b/src/writerai/types/graph_add_file_to_graph_params.py
@@ -0,0 +1,11 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["GraphAddFileToGraphParams"]
+
+
+class GraphAddFileToGraphParams(TypedDict, total=False):
+ file_id: Required[str]
diff --git a/src/writerai/types/graph_create_params.py b/src/writerai/types/graph_create_params.py
new file mode 100644
index 0000000..24638cf
--- /dev/null
+++ b/src/writerai/types/graph_create_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["GraphCreateParams"]
+
+
+class GraphCreateParams(TypedDict, total=False):
+ name: Required[str]
+
+ description: str
diff --git a/src/writerai/types/graph_create_response.py b/src/writerai/types/graph_create_response.py
new file mode 100644
index 0000000..831d298
--- /dev/null
+++ b/src/writerai/types/graph_create_response.py
@@ -0,0 +1,18 @@
+# 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__ = ["GraphCreateResponse"]
+
+
+class GraphCreateResponse(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ name: str
+
+ description: Optional[str] = None
diff --git a/src/writerai/types/graph_delete_response.py b/src/writerai/types/graph_delete_response.py
new file mode 100644
index 0000000..e689b60
--- /dev/null
+++ b/src/writerai/types/graph_delete_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+
+from .._models import BaseModel
+
+__all__ = ["GraphDeleteResponse"]
+
+
+class GraphDeleteResponse(BaseModel):
+ id: str
+
+ deleted: bool
diff --git a/src/writerai/types/graph_list_params.py b/src/writerai/types/graph_list_params.py
new file mode 100644
index 0000000..37668ef
--- /dev/null
+++ b/src/writerai/types/graph_list_params.py
@@ -0,0 +1,17 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Literal, TypedDict
+
+__all__ = ["GraphListParams"]
+
+
+class GraphListParams(TypedDict, total=False):
+ after: str
+
+ before: str
+
+ limit: int
+
+ order: Literal["asc", "desc"]
diff --git a/src/writerai/types/graph_remove_file_from_graph_response.py b/src/writerai/types/graph_remove_file_from_graph_response.py
new file mode 100644
index 0000000..bcf6d72
--- /dev/null
+++ b/src/writerai/types/graph_remove_file_from_graph_response.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+
+
+from .._models import BaseModel
+
+__all__ = ["GraphRemoveFileFromGraphResponse"]
+
+
+class GraphRemoveFileFromGraphResponse(BaseModel):
+ id: str
+
+ deleted: bool
diff --git a/src/writerai/types/graph_update_params.py b/src/writerai/types/graph_update_params.py
new file mode 100644
index 0000000..06ef5a9
--- /dev/null
+++ b/src/writerai/types/graph_update_params.py
@@ -0,0 +1,13 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+from typing_extensions import Required, TypedDict
+
+__all__ = ["GraphUpdateParams"]
+
+
+class GraphUpdateParams(TypedDict, total=False):
+ name: Required[str]
+
+ description: str
diff --git a/src/writerai/types/graph_update_response.py b/src/writerai/types/graph_update_response.py
new file mode 100644
index 0000000..63a3f42
--- /dev/null
+++ b/src/writerai/types/graph_update_response.py
@@ -0,0 +1,18 @@
+# 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__ = ["GraphUpdateResponse"]
+
+
+class GraphUpdateResponse(BaseModel):
+ id: str
+
+ created_at: datetime
+
+ name: str
+
+ description: Optional[str] = None
diff --git a/src/writerai/types/model_list_response.py b/src/writerai/types/model_list_response.py
index db27d54..cdec7b2 100644
--- a/src/writerai/types/model_list_response.py
+++ b/src/writerai/types/model_list_response.py
@@ -12,7 +12,7 @@ class Model(BaseModel):
"""The ID of the particular LLM that you want to use"""
name: str
- """The name of the particular LLM that you want to use"""
+ """The name of the particular LLM that you want to use."""
class ModelListResponse(BaseModel):
diff --git a/tests/api_resources/test_files.py b/tests/api_resources/test_files.py
new file mode 100644
index 0000000..7f19fff
--- /dev/null
+++ b/tests/api_resources/test_files.py
@@ -0,0 +1,449 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import httpx
+import pytest
+from respx import MockRouter
+
+from writerai import Writer, AsyncWriter
+from tests.utils import assert_matches_type
+from writerai.types import File, FileDeleteResponse
+from writerai._response import (
+ BinaryAPIResponse,
+ AsyncBinaryAPIResponse,
+ StreamedBinaryAPIResponse,
+ AsyncStreamedBinaryAPIResponse,
+)
+from writerai.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestFiles:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_retrieve(self, client: Writer) -> None:
+ file = client.files.retrieve(
+ "string",
+ )
+ assert_matches_type(File, file, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: Writer) -> None:
+ response = client.files.with_raw_response.retrieve(
+ "string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Writer) -> None:
+ with client.files.with_streaming_response.retrieve(
+ "string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ client.files.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Writer) -> None:
+ file = client.files.list()
+ assert_matches_type(SyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Writer) -> None:
+ file = client.files.list(
+ after="string",
+ before="string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ limit=0,
+ order="asc",
+ )
+ assert_matches_type(SyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Writer) -> None:
+ response = client.files.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(SyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Writer) -> None:
+ with client.files.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(SyncCursorPage[File], file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_delete(self, client: Writer) -> None:
+ file = client.files.delete(
+ "string",
+ )
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Writer) -> None:
+ response = client.files.with_raw_response.delete(
+ "string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Writer) -> None:
+ with client.files.with_streaming_response.delete(
+ "string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ client.files.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_method_download(self, client: Writer, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ file = client.files.download(
+ "string",
+ )
+ assert file.is_closed
+ assert file.json() == {"foo": "bar"}
+ assert cast(Any, file.is_closed) is True
+ assert isinstance(file, BinaryAPIResponse)
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_raw_response_download(self, client: Writer, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ file = client.files.with_raw_response.download(
+ "string",
+ )
+
+ assert file.is_closed is True
+ assert file.http_request.headers.get("X-Stainless-Lang") == "python"
+ assert file.json() == {"foo": "bar"}
+ assert isinstance(file, BinaryAPIResponse)
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_streaming_response_download(self, client: Writer, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ with client.files.with_streaming_response.download(
+ "string",
+ ) as file:
+ assert not file.is_closed
+ assert file.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ assert file.json() == {"foo": "bar"}
+ assert cast(Any, file.is_closed) is True
+ assert isinstance(file, StreamedBinaryAPIResponse)
+
+ assert cast(Any, file.is_closed) is True
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ def test_path_params_download(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ client.files.with_raw_response.download(
+ "",
+ )
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ def test_method_upload(self, client: Writer) -> None:
+ file = client.files.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ )
+ assert_matches_type(File, file, path=["response"])
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ def test_raw_response_upload(self, client: Writer) -> None:
+ response = client.files.with_raw_response.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ def test_streaming_response_upload(self, client: Writer) -> None:
+ with client.files.with_streaming_response.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+
+class TestAsyncFiles:
+ parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWriter) -> None:
+ file = await async_client.files.retrieve(
+ "string",
+ )
+ assert_matches_type(File, file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWriter) -> None:
+ response = await async_client.files.with_raw_response.retrieve(
+ "string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWriter) -> None:
+ async with async_client.files.with_streaming_response.retrieve(
+ "string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ await async_client.files.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWriter) -> None:
+ file = await async_client.files.list()
+ assert_matches_type(AsyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWriter) -> None:
+ file = await async_client.files.list(
+ after="string",
+ before="string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ limit=0,
+ order="asc",
+ )
+ assert_matches_type(AsyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWriter) -> None:
+ response = await async_client.files.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(AsyncCursorPage[File], file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWriter) -> None:
+ async with async_client.files.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(AsyncCursorPage[File], file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWriter) -> None:
+ file = await async_client.files.delete(
+ "string",
+ )
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWriter) -> None:
+ response = await async_client.files.with_raw_response.delete(
+ "string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWriter) -> None:
+ async with async_client.files.with_streaming_response.delete(
+ "string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(FileDeleteResponse, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ await async_client.files.with_raw_response.delete(
+ "",
+ )
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_method_download(self, async_client: AsyncWriter, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ file = await async_client.files.download(
+ "string",
+ )
+ assert file.is_closed
+ assert await file.json() == {"foo": "bar"}
+ assert cast(Any, file.is_closed) is True
+ assert isinstance(file, AsyncBinaryAPIResponse)
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_raw_response_download(self, async_client: AsyncWriter, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+
+ file = await async_client.files.with_raw_response.download(
+ "string",
+ )
+
+ assert file.is_closed is True
+ assert file.http_request.headers.get("X-Stainless-Lang") == "python"
+ assert await file.json() == {"foo": "bar"}
+ assert isinstance(file, AsyncBinaryAPIResponse)
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_streaming_response_download(self, async_client: AsyncWriter, respx_mock: MockRouter) -> None:
+ respx_mock.get("/v1/files/string/download").mock(return_value=httpx.Response(200, json={"foo": "bar"}))
+ async with async_client.files.with_streaming_response.download(
+ "string",
+ ) as file:
+ assert not file.is_closed
+ assert file.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ assert await file.json() == {"foo": "bar"}
+ assert cast(Any, file.is_closed) is True
+ assert isinstance(file, AsyncStreamedBinaryAPIResponse)
+
+ assert cast(Any, file.is_closed) is True
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ @pytest.mark.respx(base_url=base_url)
+ async def test_path_params_download(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ await async_client.files.with_raw_response.download(
+ "",
+ )
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ async def test_method_upload(self, async_client: AsyncWriter) -> None:
+ file = await async_client.files.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ )
+ assert_matches_type(File, file, path=["response"])
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ async def test_raw_response_upload(self, async_client: AsyncWriter) -> None:
+ response = await async_client.files.with_raw_response.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ file = await response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ @pytest.mark.skip(reason="requests with binary data not yet supported in test environment")
+ @parametrize
+ async def test_streaming_response_upload(self, async_client: AsyncWriter) -> None:
+ async with async_client.files.with_streaming_response.upload(
+ content=b"raw file contents",
+ content_disposition="string",
+ content_length=0,
+ content_type="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ file = await response.parse()
+ assert_matches_type(File, file, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
diff --git a/tests/api_resources/test_graphs.py b/tests/api_resources/test_graphs.py
new file mode 100644
index 0000000..4c784dd
--- /dev/null
+++ b/tests/api_resources/test_graphs.py
@@ -0,0 +1,612 @@
+# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+from __future__ import annotations
+
+import os
+from typing import Any, cast
+
+import pytest
+
+from writerai import Writer, AsyncWriter
+from tests.utils import assert_matches_type
+from writerai.types import (
+ File,
+ Graph,
+ GraphCreateResponse,
+ GraphDeleteResponse,
+ GraphUpdateResponse,
+ GraphRemoveFileFromGraphResponse,
+)
+from writerai.pagination import SyncCursorPage, AsyncCursorPage
+
+base_url = os.environ.get("TEST_API_BASE_URL", "http://127.0.0.1:4010")
+
+
+class TestGraphs:
+ parametrize = pytest.mark.parametrize("client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ def test_method_create(self, client: Writer) -> None:
+ graph = client.graphs.create(
+ name="string",
+ )
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_method_create_with_all_params(self, client: Writer) -> None:
+ graph = client.graphs.create(
+ name="string",
+ description="string",
+ )
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_create(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.create(
+ name="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_create(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.create(
+ name="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_retrieve(self, client: Writer) -> None:
+ graph = client.graphs.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(Graph, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_retrieve(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(Graph, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_retrieve(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(Graph, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_retrieve(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ client.graphs.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ def test_method_update(self, client: Writer) -> None:
+ graph = client.graphs.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ )
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_method_update_with_all_params(self, client: Writer) -> None:
+ graph = client.graphs.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ description="string",
+ )
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_update(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_update(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_update(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ client.graphs.with_raw_response.update(
+ "",
+ name="string",
+ )
+
+ @parametrize
+ def test_method_list(self, client: Writer) -> None:
+ graph = client.graphs.list()
+ assert_matches_type(SyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ def test_method_list_with_all_params(self, client: Writer) -> None:
+ graph = client.graphs.list(
+ after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ limit=0,
+ order="asc",
+ )
+ assert_matches_type(SyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_list(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(SyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_list(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(SyncCursorPage[Graph], graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_method_delete(self, client: Writer) -> None:
+ graph = client.graphs.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_delete(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_delete(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_delete(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ client.graphs.with_raw_response.delete(
+ "",
+ )
+
+ @parametrize
+ def test_method_add_file_to_graph(self, client: Writer) -> None:
+ graph = client.graphs.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ )
+ assert_matches_type(File, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_add_file_to_graph(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(File, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_add_file_to_graph(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(File, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_add_file_to_graph(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ client.graphs.with_raw_response.add_file_to_graph(
+ "",
+ file_id="string",
+ )
+
+ @parametrize
+ def test_method_remove_file_from_graph(self, client: Writer) -> None:
+ graph = client.graphs.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ @parametrize
+ def test_raw_response_remove_file_from_graph(self, client: Writer) -> None:
+ response = client.graphs.with_raw_response.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = response.parse()
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ @parametrize
+ def test_streaming_response_remove_file_from_graph(self, client: Writer) -> None:
+ with client.graphs.with_streaming_response.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = response.parse()
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ def test_path_params_remove_file_from_graph(self, client: Writer) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ client.graphs.with_raw_response.remove_file_from_graph(
+ "string",
+ graph_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ client.graphs.with_raw_response.remove_file_from_graph(
+ "",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+
+class TestAsyncGraphs:
+ parametrize = pytest.mark.parametrize("async_client", [False, True], indirect=True, ids=["loose", "strict"])
+
+ @parametrize
+ async def test_method_create(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.create(
+ name="string",
+ )
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_method_create_with_all_params(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.create(
+ name="string",
+ description="string",
+ )
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_create(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.create(
+ name="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_create(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.create(
+ name="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(GraphCreateResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_retrieve(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(Graph, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_retrieve(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(Graph, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_retrieve(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.retrieve(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(Graph, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_retrieve(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ await async_client.graphs.with_raw_response.retrieve(
+ "",
+ )
+
+ @parametrize
+ async def test_method_update(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ )
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_method_update_with_all_params(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ description="string",
+ )
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_update(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_update(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.update(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ name="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(GraphUpdateResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_update(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ await async_client.graphs.with_raw_response.update(
+ "",
+ name="string",
+ )
+
+ @parametrize
+ async def test_method_list(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.list()
+ assert_matches_type(AsyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ async def test_method_list_with_all_params(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.list(
+ after="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ before="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ limit=0,
+ order="asc",
+ )
+ assert_matches_type(AsyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_list(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.list()
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(AsyncCursorPage[Graph], graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_list(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.list() as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(AsyncCursorPage[Graph], graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_method_delete(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_delete(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_delete(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.delete(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(GraphDeleteResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_delete(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ await async_client.graphs.with_raw_response.delete(
+ "",
+ )
+
+ @parametrize
+ async def test_method_add_file_to_graph(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ )
+ assert_matches_type(File, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_add_file_to_graph(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(File, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_add_file_to_graph(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.add_file_to_graph(
+ "182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ file_id="string",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(File, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_add_file_to_graph(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ await async_client.graphs.with_raw_response.add_file_to_graph(
+ "",
+ file_id="string",
+ )
+
+ @parametrize
+ async def test_method_remove_file_from_graph(self, async_client: AsyncWriter) -> None:
+ graph = await async_client.graphs.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_raw_response_remove_file_from_graph(self, async_client: AsyncWriter) -> None:
+ response = await async_client.graphs.with_raw_response.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )
+
+ assert response.is_closed is True
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+ graph = await response.parse()
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ @parametrize
+ async def test_streaming_response_remove_file_from_graph(self, async_client: AsyncWriter) -> None:
+ async with async_client.graphs.with_streaming_response.remove_file_from_graph(
+ "string",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ ) as response:
+ assert not response.is_closed
+ assert response.http_request.headers.get("X-Stainless-Lang") == "python"
+
+ graph = await response.parse()
+ assert_matches_type(GraphRemoveFileFromGraphResponse, graph, path=["response"])
+
+ assert cast(Any, response.is_closed) is True
+
+ @parametrize
+ async def test_path_params_remove_file_from_graph(self, async_client: AsyncWriter) -> None:
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `graph_id` but received ''"):
+ await async_client.graphs.with_raw_response.remove_file_from_graph(
+ "string",
+ graph_id="",
+ )
+
+ with pytest.raises(ValueError, match=r"Expected a non-empty value for `file_id` but received ''"):
+ await async_client.graphs.with_raw_response.remove_file_from_graph(
+ "",
+ graph_id="182bd5e5-6e1a-4fe4-a799-aa6d9a6ab26e",
+ )