Typesense document destination for dlt (data load tool)
Project description
dlt-typesense
Typesense document destination for dlt — load data into Typesense collections with proper write dispositions (append, replace, merge/upsert), not as a blind reverse-ETL sink.
Why a full destination?
Typesense is treated as a document database. dlt tables map to Typesense
collections (dataset-qualified, e.g. catalog_products); rows become documents
loaded via streamed JSONL bulk import.
| Disposition | Behavior |
|---|---|
append |
Bulk import; document id from _dlt_id; action=upsert |
replace (truncate-and-insert) |
Drop + recreate the collection, then import |
merge (upsert) |
Upsert keyed by primary_key → deterministic Typesense id (uuid5) |
merge (insert-only) |
Insert keyed by _dlt_id; existing documents never modified |
skip |
Nothing is written for that table |
Merge strategies are ["upsert", "insert-only"] (upsert is the default). The
delete-insert and scd2 merge strategies and the staging replace strategies
(insert-from-staging, staging-optimized) are not supported and are
rejected with a clear capabilities error — there is no SQL layer or staging
dataset. This matches dlt's non-SQL destinations (Qdrant; insert-only follows
LanceDB): JobClientBase + JSONL load jobs + WithStateSync for incremental
pipelines. Suitable for multi-million-row syncs via file sharding and parallel
import jobs.
See docs/architecture.md for module seams and scale notes.
Install
pip install dlt-typesense
Or with uv:
uv add dlt-typesense
For local development:
git clone https://github.com/marcopesani/dlt-typesense.git
cd dlt-typesense
uv sync --group dev
Usage
import dlt
from dlt_typesense import typesense
@dlt.resource(name="products", write_disposition="merge", primary_key="sku")
def products():
yield {"sku": "A1", "title": "Widget", "price": 9.99}
pipeline = dlt.pipeline(
pipeline_name="shop",
destination=typesense(),
dataset_name="catalog",
)
pipeline.run(products())
Runnable examples are in examples/.
Start a local server with docker compose up -d first.
Schema customization (typesense_adapter)
By default collections use Typesense auto schema (a .* catch-all field).
typesense_adapter pins explicit typed fields for hinted columns and sets
collection-level options, covering the full
Typesense collections API
surface. Unhinted columns still fall through to .* auto, so schema evolution
keeps working.
from dlt_typesense import typesense, typesense_adapter
pipeline.run(
typesense_adapter(
products,
facet="category", # shorthand for {"facet": True}
sort=["price", "rating"], # shorthand for {"sort": True}
field_hints={
"description": {"locale": "de", "infix": True},
"embedding": {"type": "float[]", "num_dim": 384}, # vector field
"summary_vec": { # auto-embedding
"type": "float[]",
"embed": {
"from": ["title", "description"],
"model_config": {"model_name": "ts/e5-small"},
},
},
},
collection_hints={
"default_sorting_field": "price",
"token_separators": ["-"],
"metadata": {"owner": "search-team"},
},
)
)
field_hintsaccepts, per column:type,facet,index,optional,sort,infix,locale,stem,stem_dictionary,num_dim,vec_dist,reference,range_index,store,truncate_len,token_separators,symbols_to_index,embed.collection_hintsaccepts:default_sorting_field,token_separators,symbols_to_index,enable_nested_fields,metadata.- Unknown parameter names raise
ValueErrorwhen the adapter is called; value errors (e.g. a badlocaleorvec_dist) surface as terminal errors from the server at load time.
Credentials
Credentials resolve from .dlt/secrets.toml (or the matching
DESTINATION__TYPESENSE__CREDENTIALS__* env vars). The api_key is required;
without it the run fails with dlt's missing-config error before any load starts.
The key is never written to logs, reprs, or error messages.
[destination.typesense.credentials]
host = "localhost" # default: localhost
port = 8108 # default: 8108
protocol = "http" # default: http
api_key = "local-dev-key"
Configuration knobs
Passed to typesense(...) or resolved from config (destination.typesense.*):
| Knob | Default | Purpose |
|---|---|---|
dataset_separator |
"_" |
Separator between dataset and table in collection names (≤ 255 chars) |
client_batch_size |
1000 |
Documents per HTTP import request (client-side chunking; files are streamed, never buffered whole) |
server_batch_size |
40 |
Typesense batch_size import query parameter |
import_action |
"upsert" |
Import action. emplace updates only provided fields. Do not use create — it breaks dlt's whole-file retry idempotency |
connection_timeout_seconds |
5.0 |
Connect timeout |
read_timeout_seconds |
180.0 |
Read timeout for long import requests |
Behavior notes
- Auto schema + pinned fields. Collections are created with a
.*auto catch-all; columns hinted viatypesense_adapterare pinned as explicit typed fields ahead of it. Pinned types follow the wire format below unless overridden with atypefield hint. - Hints apply at collection creation only. First load, every
replacerun, and dev-mode/full-refresh runs (re)create collections with the current hints. Changing hints on an existingappend/mergecollection has no effect until the collection is recreated (a log line notes this). There is no schema PATCH/alter support yet. default_sorting_fieldmust resolve to a numeric (int32/int64/float) non-optional field — the destination pins it, forces it non-optional, and fails terminally otherwise. Note dltdecimal/timestampcolumns are stored as strings; use adouble/bigintcolumn or override the fieldtype. Rows with a null in that column fail at import.- Vector/native fields. An array or object
typehint (float[],string[],object,geopoint, …) marks the dlt column asjsonso lists stay inline instead of becoming child collections, and the value is imported as native JSON rather than a string — this is hownum_dimvectors work end-to-end. Withimport_action="emplace", partial updates that omit a non-optional pinned field are rejected by the server. - Auto-embedding (
embed) fields make the server download the model at collection creation — expect a slow first create. - Child tables under merge. Merge updates root documents in place. When nested-list items disappear from the source, orphaned child-table documents are not deleted. Root documents stay correct.
- Reserved
id. Typesense reserves the top-level documentid, which this destination manages (from_dlt_idor the merge key). A source column namedidis renamed to__idby the naming convention, so its value is preserved and never silently overwritten. - Type representations. dlt's JSONL wire format is stored as-is under auto
schema:
decimal/weias exact strings,timestamp/date/timeas ISO-8601 strings,binaryas base64,jsoncolumns as one canonical JSON string. Bigints round-trip exactly as Typesense int64. - Merge without a key. A
mergetable with neither aprimary_keynor auniquecolumn cannot form a deterministic id and fails with a terminal error rather than silently loading duplicates. - Replace is drop + recreate. A concurrent reader can observe an empty collection window mid-replace.
Development
uv sync --group dev
uv run ruff check .
uv run ruff format --check .
uv run basedpyright
uv run pytest # unit tests (no server)
docker compose up -d # start Typesense for integration tests
uv run pytest -m integration # integration tests (fail, never skip, if unreachable)
Contributing
See CONTRIBUTING.md. By contributing you agree to the Developer Certificate of Origin (sign off commits with Signed-off-by).
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dlt_typesense-0.1.0.tar.gz.
File metadata
- Download URL: dlt_typesense-0.1.0.tar.gz
- Upload date:
- Size: 154.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7dd25e3ca484d3bcc6ba910be26847e0dbf79fde4a146b42f3a0106e181fd9b1
|
|
| MD5 |
45897410bf7ba24402c30093f3ba2dd9
|
|
| BLAKE2b-256 |
5a10b5fe149d2ac2c8864b8246410af768773db4ac975088e047e41d9d960f6d
|
Provenance
The following attestation bundles were made for dlt_typesense-0.1.0.tar.gz:
Publisher:
publish.yml on marcopesani/dlt-typesense
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlt_typesense-0.1.0.tar.gz -
Subject digest:
7dd25e3ca484d3bcc6ba910be26847e0dbf79fde4a146b42f3a0106e181fd9b1 - Sigstore transparency entry: 2187544979
- Sigstore integration time:
-
Permalink:
marcopesani/dlt-typesense@b8034eaddf92031216289e5b9e94219027d11870 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/marcopesani
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8034eaddf92031216289e5b9e94219027d11870 -
Trigger Event:
release
-
Statement type:
File details
Details for the file dlt_typesense-0.1.0-py3-none-any.whl.
File metadata
- Download URL: dlt_typesense-0.1.0-py3-none-any.whl
- Upload date:
- Size: 27.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b269288a1830ab253f38b2e0d5cfdae5159f2dd1bf3a6f7d3d6f61f0ddf383f8
|
|
| MD5 |
ff4d848fa4903655a3a4fb9635f20d59
|
|
| BLAKE2b-256 |
121e7b0f3806e04814885c575b6cc55cb47cb1f66bc2bcb45b17b98cba686db1
|
Provenance
The following attestation bundles were made for dlt_typesense-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on marcopesani/dlt-typesense
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dlt_typesense-0.1.0-py3-none-any.whl -
Subject digest:
b269288a1830ab253f38b2e0d5cfdae5159f2dd1bf3a6f7d3d6f61f0ddf383f8 - Sigstore transparency entry: 2187545041
- Sigstore integration time:
-
Permalink:
marcopesani/dlt-typesense@b8034eaddf92031216289e5b9e94219027d11870 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/marcopesani
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@b8034eaddf92031216289e5b9e94219027d11870 -
Trigger Event:
release
-
Statement type: