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
4 changes: 0 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,6 @@ repos:
name: vale sync
pass_filenames: false
args: [sync, "--config=.github/vale.ini"]
- id: vale
name: Spell and Style Check with Vale
args: ["--config=.github/vale.ini"]
files: \.md$
- repo: https://github.com/astral-sh/uv-pre-commit
# uv version, https://github.com/astral-sh/uv-pre-commit/releases
rev: 0.8.19
Expand Down
6 changes: 6 additions & 0 deletions docling-serve-client/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.venv/
.mypy_cache/
.pytest_cache/
.ruff_cache/
*.egg-info/
__pycache__/
28 changes: 28 additions & 0 deletions docling-serve-client/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
fail_fast: true
repos:
- repo: local
hooks:
- id: client-ruff-format
name: Ruff formatter
entry: /bin/zsh -lc 'cd "$(git rev-parse --show-toplevel)/docling-serve-client" && uv run --project . --no-sync ruff format --check --config pyproject.toml docling tests examples'
pass_filenames: false
language: system
always_run: true
- id: client-ruff
name: Ruff linter
entry: /bin/zsh -lc 'cd "$(git rev-parse --show-toplevel)/docling-serve-client" && uv run --project . --no-sync ruff check --fix --config pyproject.toml docling tests examples'
pass_filenames: false
language: system
always_run: true
- id: client-mypy
name: MyPy
entry: /bin/zsh -lc 'cd "$(git rev-parse --show-toplevel)/docling-serve-client" && uv run --project . --no-sync mypy --explicit-package-bases docling'
pass_filenames: false
language: system
always_run: true
- id: client-uv-lock
name: uv lock
entry: /bin/zsh -lc 'cd "$(git rev-parse --show-toplevel)/docling-serve-client" && uv lock --project .'
pass_filenames: false
language: system
always_run: true
40 changes: 40 additions & 0 deletions docling-serve-client/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Docling Serve Client SDK

`docling-serve-client` is the standalone client SDK distribution for
`docling-serve`.

```python
from docling.service_client import DoclingServiceClient
```

The package depends on the published `docling-serve` package for shared request
and response models.

## Install From A Repo Branch

For now, install this client directly from this repository and select both the git ref and the
client package subdirectory:

```bash
pip install "docling-serve-client @ git+https://github.com/docling-project/docling-serve.git@main#subdirectory=docling-serve-client"
```

## Local Development

From this package directory:

```bash
uv sync
uv run pytest
uv run pre-commit run --all-files
```

## Examples

The package includes its own test fixtures and examples.

```bash
uv run python examples/convert_compat.py
uv run python examples/task_api.py
uv run python examples/batch_and_chunk.py
```
43 changes: 43 additions & 0 deletions docling-serve-client/docling/service_client/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Client SDK for interacting with docling-serve."""

from docling.service_client.client import (
ChunkerKind,
ConversionItem,
DoclingServiceClient,
HealthResponse,
RawServiceResult,
StatusWatcherKind,
VersionResponse,
)
from docling.service_client.exceptions import (
BatchConversionError,
ConversionError,
DoclingServiceClientError,
ResultExpiredError,
ResultNotReadyError,
ServiceError,
ServiceUnavailableError,
TaskNotFoundError,
TaskTimeoutError,
)
from docling.service_client.job import ConversionJob

__all__ = [
"BatchConversionError",
"ChunkerKind",
"ConversionError",
"ConversionItem",
"ConversionJob",
"DoclingServiceClient",
"DoclingServiceClientError",
"HealthResponse",
"RawServiceResult",
"ResultExpiredError",
"ResultNotReadyError",
"ServiceError",
"ServiceUnavailableError",
"StatusWatcherKind",
"TaskNotFoundError",
"TaskTimeoutError",
"VersionResponse",
]
60 changes: 60 additions & 0 deletions docling-serve-client/docling/service_client/_scheduler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"""Internal bounded async scheduling helpers for client-side batch work."""

from __future__ import annotations

import asyncio
from collections.abc import AsyncIterator, Awaitable, Callable
from typing import TypeVar

import httpx

T_Item = TypeVar("T_Item")
T_Result = TypeVar("T_Result")


async def _run_bounded(
items: list[T_Item],
process_one: Callable[[int, T_Item, httpx.AsyncClient], Awaitable[T_Result]],
async_client: httpx.AsyncClient,
max_in_flight: int,
) -> AsyncIterator[tuple[int, T_Result | BaseException]]:
"""Yield item outcomes in completion order with bounded concurrency."""

if not items:
return

worker_count = min(len(items), max(1, max_in_flight))
queue: asyncio.Queue[tuple[int, T_Result | BaseException]] = asyncio.Queue()
index_lock = asyncio.Lock()
next_idx = 0

async def worker() -> None:
nonlocal next_idx
while True:
async with index_lock:
if next_idx >= len(items):
return
idx = next_idx
next_idx += 1

item = items[idx]
try:
result = await process_one(idx, item, async_client)
except asyncio.CancelledError:
raise
except BaseException as exc:
await queue.put((idx, exc))
else:
await queue.put((idx, result))

tasks = [asyncio.create_task(worker()) for _ in range(worker_count)]
completed = 0
try:
while completed < len(items):
yield await queue.get()
completed += 1
finally:
for task in tasks:
if not task.done():
task.cancel()
await asyncio.gather(*tasks, return_exceptions=True)
Loading
Loading