Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c38aefb
fix(document): fix types for document deletion
tharropoulos Nov 17, 2025
fdd23b3
chore: bump mypy
tharropoulos Dec 4, 2025
4ea85c9
chore: add httpx
tharropoulos Dec 4, 2025
8a54688
chore(tests): remove redudant mocked tests
tharropoulos Dec 4, 2025
17af1bf
test(curation_set): add missing integration tests
tharropoulos Dec 4, 2025
ad2a7fd
test(synonym_set): add missing integration tests
tharropoulos Dec 4, 2025
a0b8d70
refactor: migrate request handler from requests to httpx with async s…
tharropoulos Dec 8, 2025
944846e
chore: update test dependencies for httpx migration
tharropoulos Dec 8, 2025
d5b656d
refactor: migrate api_call from requests to httpx and add async support
tharropoulos Dec 8, 2025
2795e3a
feat(alias): add async support for alias operations
tharropoulos Dec 8, 2025
af2e72e
feat(analytics): add async support for analytics operations
tharropoulos Dec 8, 2025
071cefe
feat(analyticsV1): add async support for analytics v1 operations
tharropoulos Dec 8, 2025
d72f3d4
refactor(collection): make TDoc typevar covariant in collection classes
tharropoulos Dec 8, 2025
aa95966
feat(collection): add async support for collection operations
tharropoulos Dec 8, 2025
6a52a43
feat(convo): add async support for conversation model operations
tharropoulos Dec 8, 2025
f06f226
feat(curation): add async support for curation set operations
tharropoulos Dec 8, 2025
fc7ea1c
feat(debug): add async support for debug operations
tharropoulos Dec 8, 2025
977f032
feat(documents): add async support for document operations
tharropoulos Dec 8, 2025
78774fe
feat(api-keys): add async support for key operations
tharropoulos Dec 8, 2025
c4347f8
feat(metrics): add async support for metrics operations
tharropoulos Dec 8, 2025
b214785
feat(multi-search): add async support for multi-search operations
tharropoulos Dec 8, 2025
e367722
feat(nl-search): add async support for nl search model operations
tharropoulos Dec 8, 2025
e3e6c82
feat(ops): add async support for operations
tharropoulos Dec 8, 2025
b6f9843
feat(overrides): add async support for override operations
tharropoulos Dec 8, 2025
efc11b1
feat(synonyms): add async support for synonym operations
tharropoulos Dec 8, 2025
fbd3995
feat(synonym-set): add async support for synonym set operations
tharropoulos Dec 8, 2025
c742abc
lint: remove annotations imports
tharropoulos Dec 8, 2025
dc0d9e0
chore: bump deps
tharropoulos Dec 8, 2025
8ad2414
feat(stemming): add async support for stemming operations
tharropoulos Dec 8, 2025
ae23d41
feat(stopwords): add async support for stopwords operations
tharropoulos Dec 8, 2025
4e0b5c6
feat(client): expose async client class
tharropoulos Dec 8, 2025
8dffb3f
chore: bump version to 2.0.0
tharropoulos Dec 9, 2025
f63292b
chore(test): remove unused imports on test files
tharropoulos Feb 3, 2026
947f263
feat: expose async client to entrypoint
tharropoulos Feb 3, 2026
8e8c1df
chore: remove sync version of the client
tharropoulos Feb 4, 2026
f46d6b7
refactor: move async version of the client to dedicated module
tharropoulos Feb 4, 2026
b5211ad
feat(types): add metrics types
tharropoulos Feb 4, 2026
5b54389
refactor: refactor imports on entrypoint
tharropoulos Feb 4, 2026
a70c271
feat: add entrypoint for sync client
tharropoulos Feb 4, 2026
133bb02
chore: add unasync to dependencies
tharropoulos Feb 4, 2026
54ab4d3
feat(utils): add unasync util to generate sync version of the client
tharropoulos Feb 4, 2026
33eed01
chore: update imports on tests
tharropoulos Feb 4, 2026
2b8b9b2
feat(utils): add diffing to unasync util
tharropoulos Feb 4, 2026
aa8fa92
fix(utils): remove `async` references in sync client
tharropoulos Feb 4, 2026
be3428e
ci: add unasync check on ci
tharropoulos Feb 4, 2026
5845787
fix(types): fix type casting for generic document types
tharropoulos Feb 4, 2026
dd4c625
fix(types): give defaut to `as_json` to be true as it's needed
tharropoulos Feb 4, 2026
34314ba
fix(types): fix union casting for analytics v1
tharropoulos Feb 4, 2026
d42637e
fix(types): use `[]` attribute access instead of `.get` for alias
tharropoulos Feb 4, 2026
95929db
chore: generate sync version of the client
tharropoulos Feb 4, 2026
0e4cba8
feat(examples): add async example
tharropoulos Feb 4, 2026
ca35345
docs(readme): add section for async
tharropoulos Feb 4, 2026
724314d
docs(readme): update compatibility list
tharropoulos Feb 4, 2026
604d910
docs(readme): add mention of codegen for sync version of the client
tharropoulos Feb 4, 2026
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
3 changes: 3 additions & 0 deletions .github/workflows/test-and-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- name: Install the project
run: uv sync --locked --all-extras --dev

- name: Check sync generation
run: uv run python utils/run-unasync.py --check

- name: Lint with Ruff
run: |
uv run ruff check src/typesense
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,38 @@ You can find some examples [here](https://github.com/typesense/typesense-python/

See detailed [API documentation](https://typesense.org/api).

## Async usage

Use `AsyncClient` when working in an async runtime:

```python
import asyncio
import typesense


async def main() -> None:
client = typesense.AsyncClient({
"api_key": "abcd",
"nodes": [{"host": "localhost", "port": "8108", "protocol": "http"}],
"connection_timeout_seconds": 2,
})

print(await client.collections.retrieve())
await client.api_call.aclose()


if __name__ == "__main__":
asyncio.run(main())
```

See `examples/async_collection_operations.py` for a fuller async walkthrough.

## Compatibility

| Typesense Server | typesense-python |
|------------------|------------------|
| \>= v30.0 | \>= v2.0.0 |
| \>= v28.0 | \>= v1.0.0 |
| \>= v26.0 | \>= v0.20.0 |
| \>= v0.25.0 | \>= v0.16.0 |
| \>= v0.23.0 | \>= v0.14.0 |
Expand All @@ -32,7 +60,11 @@ See detailed [API documentation](https://typesense.org/api).

## Contributing

> [!NOTE]
> Development happens in async-only code; sync code is generated automatically via `utils/run-unasync.py`.
Bug reports and pull requests are welcome on GitHub at [https://github.com/typesense/typesense-python].
If you change any part of the client's source code, run `uv run utils/run-unasync.py` before opening a PR to keep the generated sync files in sync.

## License

Expand Down
196 changes: 196 additions & 0 deletions examples/async_collection_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import asyncio
import json
import os
import sys

curr_dir = os.path.dirname(os.path.realpath(__file__))
repo_root = os.path.abspath(os.path.join(curr_dir, os.pardir))
sys.path.insert(1, os.path.join(repo_root, "src"))

import typesense
from typesense.exceptions import TypesenseClientError


async def main() -> None:
client = typesense.AsyncClient(
{
"api_key": "xyz",
"nodes": [
{
"host": "localhost",
"port": "8108",
"protocol": "http",
}
],
"connection_timeout_seconds": 2,
}
)

try:
# Drop pre-existing collection if any
try:
await client.collections["books"].delete()
except Exception:
pass

# Create a collection
create_response = await client.collections.create(
{
"name": "books",
"fields": [
{"name": "title", "type": "string"},
{"name": "authors", "type": "string[]", "facet": True},
{"name": "publication_year", "type": "int32", "facet": True},
{"name": "ratings_count", "type": "int32"},
{"name": "average_rating", "type": "float"},
{"name": "image_url", "type": "string"},
],
"default_sorting_field": "ratings_count",
}
)

print(create_response)

# Retrieve the collection we just created
retrieve_response = await client.collections["books"].retrieve()
print(retrieve_response)

# Try retrieving all collections
retrieve_all_response = await client.collections.retrieve()
print(retrieve_all_response)

# Add a book
hunger_games_book = {
"id": "1",
"authors": ["Suzanne Collins"],
"average_rating": 4.34,
"publication_year": 2008,
"title": "The Hunger Games",
"image_url": "https://images.gr-assets.com/books/1447303603m/2767052.jpg",
"ratings_count": 4780653,
}

await client.collections["books"].documents.create(hunger_games_book)

# Upsert the same document
print(await client.collections["books"].documents.upsert(hunger_games_book))

# Or update it
hunger_games_book_updated = {"id": "1", "average_rating": 4.45}
print(
await client.collections["books"]
.documents["1"]
.update(hunger_games_book_updated)
)

# Try updating with bad data (with coercion enabled)
hunger_games_book_updated = {"id": "1", "average_rating": "4.55"}
print(
await client.collections["books"]
.documents["1"]
.update(hunger_games_book_updated, {"dirty_values": "coerce_or_reject"})
)

# Export the documents from a collection
export_output = await client.collections["books"].documents.export()
print(export_output)

# Fetch a document in a collection
print(await client.collections["books"].documents["1"].retrieve())

# Search for documents in a collection
print(
await client.collections["books"].documents.search(
{
"q": "hunger",
"query_by": "title",
"sort_by": "ratings_count:desc",
}
)
)

# Make multiple search requests at the same time
print(
await client.multi_search.perform(
{
"searches": [
{
"q": "hunger",
"query_by": "title",
},
{
"q": "suzanne",
"query_by": "authors",
},
]
},
{"collection": "books", "sort_by": "ratings_count:desc"},
)
)

# Remove a document from a collection
print(await client.collections["books"].documents["1"].delete())

# Import documents into a collection
docs_to_import = []
for exported_doc_str in export_output.split("\n"):
docs_to_import.append(json.loads(exported_doc_str))

import_results = await client.collections["books"].documents.import_(
docs_to_import
)
print(import_results)

# Upserting documents
import_results = await client.collections["books"].documents.import_(
docs_to_import,
{
"action": "upsert",
"return_id": True,
},
)
print(import_results)

# Schema change: add optional field
schema_change = {
"fields": [{"name": "in_stock", "optional": True, "type": "bool"}]
}
print(await client.collections["books"].update(schema_change))

# Update value matching a filter
updated_doc = {"publication_year": 2009}
print(
await client.collections["books"].documents.update(
updated_doc, {"filter_by": "publication_year: 2008"}
)
)

# Drop the field
schema_change = {"fields": [{"name": "in_stock", "drop": True}]}
print(await client.collections["books"].update(schema_change))

# Deleting documents matching a filter query
print(
await client.collections["books"].documents.delete(
{"filter_by": "ratings_count: 4780653"}
)
)

# Try importing empty list
try:
import_results = await client.collections["books"].documents.import_(
[], {"action": "upsert"}
)
print(import_results)
except TypesenseClientError:
print("Detected import of empty document list.")

# Drop the collection
drop_response = await client.collections["books"].delete()
print(drop_response)
finally:
await client.api_call.aclose()


if __name__ == "__main__":
asyncio.run(main())
13 changes: 9 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ classifiers = [
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
dependencies = ["requests", "typing-extensions"]
dependencies = [
"httpx>=0.28.1",
"typing-extensions",
]
dynamic = ["version"]

[project.urls]
Expand All @@ -35,16 +38,18 @@ build-backend = "setuptools.build_meta"

[dependency-groups]
dev = [
"mypy",
"mypy>=1.19.0",
"pytest",
"pytest-asyncio",
"coverage",
"pytest-mock",
"requests-mock",
"python-dotenv",
"types-requests",
"faker",
"ruff>=0.11.11",
"isort>=6.0.1",
"respx>=0.22.0",
"requests",
"unasync>=0.6.0",
]

[tool.uv]
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[pytest]
pythonpath = src
asyncio_mode = auto
markers =
open_ai
7 changes: 4 additions & 3 deletions src/typesense/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .client import Client # NOQA
from .sync.client import Client # NOQA
from .async_.client import AsyncClient # NOQA


__all__ = ["Client"]
__version__ = "1.3.0"
__version__ = "2.0.0"
__all__ = ["Client", "AsyncClient"]
84 changes: 0 additions & 84 deletions src/typesense/alias.py

This file was deleted.

Loading