Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 70 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ and offers both synchronous and asynchronous clients powered by [httpx](https://

## Documentation

The REST API documentation can be found on [autorender.mintlify.app](https://autorender.mintlify.app/). The full API of this library can be found in [api.md](api.md).
The REST API documentation can be found on [autorender.io](https://autorender.io/docs). The full API of this library can be found in [api.md](api.md).

## Installation

Expand All @@ -30,10 +30,10 @@ client = Autorender(
api_key=os.environ.get("AUTORENDER_API_KEY"), # This is the default and can be omitted
)

files = client.files.list(
page = client.files.list(
limit=10,
)
print(files.files)
print(page.files)
```

While you can provide an `api_key` keyword argument,
Expand All @@ -56,10 +56,10 @@ client = AsyncAutorender(


async def main() -> None:
files = await client.files.list(
page = await client.files.list(
limit=10,
)
print(files.files)
print(page.files)


asyncio.run(main())
Expand Down Expand Up @@ -92,10 +92,10 @@ async def main() -> None:
api_key=os.environ.get("AUTORENDER_API_KEY"), # This is the default and can be omitted
http_client=DefaultAioHttpClient(),
) as client:
files = await client.files.list(
page = await client.files.list(
limit=10,
)
print(files.files)
print(page.files)


asyncio.run(main())
Expand All @@ -110,6 +110,69 @@ Nested request parameters are [TypedDicts](https://docs.python.org/3/library/typ

Typed requests and responses provide autocomplete and documentation within your editor. If you would like to see type errors in VS Code to help catch bugs earlier, set `python.analysis.typeCheckingMode` to `basic`.

## Pagination

List methods in the Autorender API are paginated.

This library provides auto-paginating iterators with each list response, so you do not have to request successive pages manually:

```python
from autorender import Autorender

client = Autorender()

all_files = []
# Automatically fetches more pages as needed.
for file in client.files.list():
# Do something with file here
all_files.append(file)
print(all_files)
```

Or, asynchronously:

```python
import asyncio
from autorender import AsyncAutorender

client = AsyncAutorender()


async def main() -> None:
all_files = []
# Iterate through items across all pages, issuing requests as needed.
async for file in client.files.list():
all_files.append(file)
print(all_files)


asyncio.run(main())
```

Alternatively, you can use the `.has_next_page()`, `.next_page_info()`, or `.get_next_page()` methods for more granular control working with pages:

```python
first_page = await client.files.list()
if first_page.has_next_page():
print(f"will fetch next page using these details: {first_page.next_page_info()}")
next_page = await first_page.get_next_page()
print(f"number of items we just fetched: {len(next_page.files)}")

# Remove `await` for non-async usage.
```

Or just work directly with the returned data:

```python
first_page = await client.files.list()

print(f"page number: {first_page.meta.page}") # => "page number: 1"
for file in first_page.files:
Comment on lines +167 to +170

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Guard access to optional meta.

first_page.meta can be None. Line 169 can raise AttributeError for a response without metadata. Guard meta before reading meta.page, or document that this endpoint always returns metadata.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 167 - 170, Update the README example around
first_page so accessing the optional meta field cannot raise an AttributeError;
guard first_page.meta before reading page, while preserving the existing
page-number output when metadata is present.

print(file.id)

# Remove `await` for non-async usage.
```

## File uploads

Request parameters that correspond to file uploads can be passed as `bytes`, or a [`PathLike`](https://docs.python.org/3/library/os.html#os.PathLike) instance or a tuple of `(filename, contents, media type)`.
Expand Down
2 changes: 1 addition & 1 deletion api.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ from autorender.types import FileRetrieveResponse, FileListResponse, FileRenameR
Methods:

- <code title="get /api/v1/files/{fileNo}">client.files.<a href="./src/autorender/resources/files.py">retrieve</a>(file_no) -> <a href="./src/autorender/types/file_retrieve_response.py">FileRetrieveResponse</a></code>
- <code title="get /api/v1/files">client.files.<a href="./src/autorender/resources/files.py">list</a>(\*\*<a href="src/autorender/types/file_list_params.py">params</a>) -> <a href="./src/autorender/types/file_list_response.py">FileListResponse</a></code>
- <code title="get /api/v1/files">client.files.<a href="./src/autorender/resources/files.py">list</a>(\*\*<a href="src/autorender/types/file_list_params.py">params</a>) -> <a href="./src/autorender/types/file_list_response.py">SyncPagePagination[FileListResponse]</a></code>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Link SyncPagePagination to its definition.

Line 25 links SyncPagePagination[FileListResponse] to file_list_response.py. That file does not define SyncPagePagination. Link the wrapper to src/autorender/pagination.py and link FileListResponse separately.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api.md` at line 25, Update the return-type link in the files list API
documentation so SyncPagePagination links to its definition in pagination.py,
while FileListResponse remains separately linked to file_list_response.py.

- <code title="delete /api/v1/files/{fileNo}">client.files.<a href="./src/autorender/resources/files.py">delete</a>(file_no) -> None</code>
- <code title="patch /api/v1/files/{fileNo}/rename">client.files.<a href="./src/autorender/resources/files.py">rename</a>(file_no, \*\*<a href="src/autorender/types/file_rename_params.py">params</a>) -> <a href="./src/autorender/types/file_rename_response.py">FileRenameResponse</a></code>

Expand Down
69 changes: 42 additions & 27 deletions src/autorender/pagination.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,69 +3,84 @@
from typing import List, Generic, TypeVar, Optional
from typing_extensions import override

from pydantic import Field as FieldInfo

from ._models import BaseModel
from ._base_client import BasePage, PageInfo, BaseSyncPage, BaseAsyncPage

__all__ = ["SyncPagePagination", "AsyncPagePagination"]
__all__ = ["PagePaginationMeta", "SyncPagePagination", "AsyncPagePagination"]

_T = TypeVar("_T")


class PagePaginationMeta(BaseModel):
has_next: Optional[bool] = FieldInfo(alias="hasNext", default=None)

page: Optional[int] = None


class SyncPagePagination(BaseSyncPage[_T], BasePage[_T], Generic[_T]):
data: List[_T]
current_page: Optional[int] = None
has_next: Optional[bool] = None
total_results: Optional[int] = None
files: List[_T]
meta: Optional[PagePaginationMeta] = None

@override
def _get_page_items(self) -> List[_T]:
data = self.data
if not data:
files = self.files
if not files:
return []
return data
return files

@override
def has_next_page(self) -> bool:
items = self._get_page_items()
if not items:
has_next = None
if self.meta is not None:
if self.meta.has_next is not None:
has_next = self.meta.has_next
if has_next is not None and has_next is False:
return False
if self.has_next is not None:
return self.has_next
return self.next_page_info() is not None

return super().has_next_page()

@override
def next_page_info(self) -> Optional[PageInfo]:
current_page = self.current_page
current_page = None
if self.meta is not None:
if self.meta.page is not None:
current_page = self.meta.page
if current_page is None:
current_page = 1

return PageInfo(params={"page": current_page + 1})


class AsyncPagePagination(BaseAsyncPage[_T], BasePage[_T], Generic[_T]):
data: List[_T]
current_page: Optional[int] = None
has_next: Optional[bool] = None
total_results: Optional[int] = None
files: List[_T]
meta: Optional[PagePaginationMeta] = None

@override
def _get_page_items(self) -> List[_T]:
data = self.data
if not data:
files = self.files
if not files:
return []
return data
return files

@override
def has_next_page(self) -> bool:
items = self._get_page_items()
if not items:
has_next = None
if self.meta is not None:
if self.meta.has_next is not None:
has_next = self.meta.has_next
if has_next is not None and has_next is False:
return False
if self.has_next is not None:
return self.has_next
return self.next_page_info() is not None

return super().has_next_page()

@override
def next_page_info(self) -> Optional[PageInfo]:
current_page = self.current_page
current_page = None
if self.meta is not None:
if self.meta.page is not None:
current_page = self.meta.page
if current_page is None:
current_page = 1

Expand Down
21 changes: 12 additions & 9 deletions src/autorender/resources/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
async_to_raw_response_wrapper,
async_to_streamed_response_wrapper,
)
from .._base_client import make_request_options
from ..pagination import SyncPagePagination, AsyncPagePagination
from .._base_client import AsyncPaginator, make_request_options
from ..types.file_list_response import FileListResponse
from ..types.file_rename_response import FileRenameResponse
from ..types.file_retrieve_response import FileRetrieveResponse
Expand Down Expand Up @@ -95,7 +96,7 @@ def list(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileListResponse:
) -> SyncPagePagination[FileListResponse]:
"""
List/search files with pagination, filtering, and sorting.

Expand All @@ -112,8 +113,9 @@ def list(

timeout: Override the client-level default timeout for this request, in seconds
"""
return self._get(
return self._get_api_list(
"/api/v1/files",
page=SyncPagePagination[FileListResponse],
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
Expand All @@ -130,7 +132,7 @@ def list(
file_list_params.FileListParams,
),
),
cast_to=FileListResponse,
model=FileListResponse,
)

def delete(
Expand Down Expand Up @@ -260,7 +262,7 @@ async def retrieve(
cast_to=FileRetrieveResponse,
)

async def list(
def list(
self,
*,
folder_no: str | Omit = omit,
Expand All @@ -275,7 +277,7 @@ async def list(
extra_query: Query | None = None,
extra_body: Body | None = None,
timeout: float | httpx.Timeout | None | NotGiven = not_given,
) -> FileListResponse:
) -> AsyncPaginator[FileListResponse, AsyncPagePagination[FileListResponse]]:
"""
List/search files with pagination, filtering, and sorting.

Expand All @@ -292,14 +294,15 @@ async def list(

timeout: Override the client-level default timeout for this request, in seconds
"""
return await self._get(
return self._get_api_list(
"/api/v1/files",
page=AsyncPagePagination[FileListResponse],
options=make_request_options(
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
timeout=timeout,
query=await async_maybe_transform(
query=maybe_transform(
{
"folder_no": folder_no,
"limit": limit,
Expand All @@ -310,7 +313,7 @@ async def list(
file_list_params.FileListParams,
),
),
cast_to=FileListResponse,
model=FileListResponse,
)

async def delete(
Expand Down
26 changes: 2 additions & 24 deletions src/autorender/types/file_list_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@
from typing import Dict, List, Optional
from datetime import datetime

from pydantic import Field as FieldInfo

from .._models import BaseModel

__all__ = ["FileListResponse", "File", "Meta"]
__all__ = ["FileListResponse"]


class File(BaseModel):
class FileListResponse(BaseModel):
id: str

created_at: datetime
Expand Down Expand Up @@ -44,23 +42,3 @@ class File(BaseModel):
url: str

width: Optional[int] = None


class Meta(BaseModel):
has_next: bool = FieldInfo(alias="hasNext")

has_prev: bool = FieldInfo(alias="hasPrev")

limit: int

page: int

total: int


class FileListResponse(BaseModel):
"""Files list"""

files: List[File]

meta: Meta
Loading