Changelog¶
All notable changes to this project will be documented in this file.
The format follows Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased¶
[0.4.1] - 2026-07-13¶
Changed¶
- Lowered the minimum supported Python version from 3.14 to 3.12.
The two
except JSONDecodeError, KeyError:clauses insrc/ineepy/_auth.py(PEP 758, 3.14-only) are now parenthesized. CI now runs the test suite across 3.12, 3.13, and 3.14; local development continues to use 3.14 (.python-version).
[0.4.0] - 2026-07-10¶
Added¶
data.poll_download_to(job_id, path)(sync and async) streams a query result directly to disk instead of buffering it fully in memory.data.upload/update/overwrite/upsert/append(and theirsubmit_*counterparts) now accept a file-like object or iterable of bytes in addition tobytes, so large uploads can be streamed from disk without loading the whole payload into memory. Async methods acceptbytesor anAsyncIterable[bytes](httpx cannot send a sync file-like/iterator on an async client).ineepynow exportsUserResponse,UsersListResponse,APIKeyResponse,APIKeysListResponse,NamespaceResponse,NamespacesListResponse,TableResponse,TablesListResponse,DataJobResponse,DatasetInfoResponse, andColumnSchemaat the top level.ineepy.modelsnow also exports every other request/response model (NamespaceResponseMinimal,NamespaceMetadata,TableResponseSummary,TableResponseMinimal,QueryJobResponse,PaginationMetadata,Filter,Token,UpdateOptions), so every public model is importable from a public path instead of onlyineepy._models.data.querynow has precise@overloadsignatures:format='json'(default) returnsQueryResponse | None,format='parquet'|'csv'returnsbytes. Runtime behavior is unchanged.
Changed¶
tables.update_schemasignature is now(table_id, columns, etag=None);etagis optional and auto-fetched like every other update method.- BREAKING:
users.createnow returns a singleUserResponseinstead oflist[UserResponse]. - BREAKING:
get_token/async_get_token/api_keys.createnow always return the access token string. Previously they returnedNonewhensave_token=True; now the token is saved toineepy.toml(as before) AND returned. - BREAKING:
QueryResponse.itemsis now typedlist[dict[str, Any]]instead oflist[Any]. - Every parameter that previously required a sequence of strings
(
scopes,tags,sources,key_columns,filters,order_by) now also accepts a single string across all resources — previously a bare string was silently iterated character-by-character (e.g.scopes='editor'became['e', 'd', 'i', 't', 'o', 'r']). config_fileparameters (Ineepy,AsyncIneepy,get_token,async_get_token,api_keys.create) now acceptstrin addition toPath.api_keys.create(expires_at=...)now accepts an ISO-8601 string in addition todatetime.data.submit_cross_query/data.cross_querynow accept a plaindict/mapping in addition toCrossQueryRequest.- Async methods now carry their own concise docstrings (visible in
IDE hover and in the rendered docs) instead of a runtime
__doc__copy that static tooling (mkdocstrings, IDE hover) couldn't see. This also changeshelp()/.__doc__at runtime: async methods now show the short form instead of the full sync text. Filter.valueandFilterLeaf.valuenow share the widened typestr | bool | int | float | datetime | None(previously one lackedfloat, the otherdatetime);datetimefilter values are serialized to ISO-8601 strings in cross-query bodies.
Fixed¶
- The underlying
httpxclient is now closed when the startup health check fails, instead of leaking the connection pool. get_token/async_get_tokennow use the library-wide 60 s timeout instead of httpx's 5 s default, fixing spuriousReadTimeouton slow auth endpoints.data.poll_downloadraises a typedJobFailedErrorwhen a redirect response is missing itsLocationheader, instead of crashing with a bareKeyError.write_confignow escapes quotes, backslashes, and control characters, so saved config values can no longer corruptineepy.toml.tables.update_metadatano longer resetstitle,sensitivity, andrefresh_frequencywhen they are not passed.exceptions.TimeoutErrornow also subclasses the builtinTimeoutError, soexcept TimeoutError:using the builtin catches it too.tables._meta_body's type annotations now correctly reflectupdate_metadata's optional parameters (fixes 6pyrighterrors introduced by theupdate_metadatapatch-semantics fix above).- Fixed
pyrighterrors in_config.py(settings_customise_sourcesoverride signature) and_resources/_base.py(S3 uploadseek/tellduck-typing) —make lintnow runspyrightin addition toruff.
Removed¶
- Broken
ineepyconsole script (declared entry point had no implementation). NamespaceResponseSummarymodel (field-identical toNamespaceResponse, unreachable via pydantic's union resolution).
Documentation¶
- README rewritten: every example is now copy-paste correct against
the current API (the old version called a nonexistent
users.me(), named an unreadINEEPY_API_VERSIONenv var, and misstated the default base URL), and it now covers theineepy.tomlflow, data upload/query/streaming, configuration precedence, and typed error handling. - The three "WIP" guide stubs are now full guides: Create & use an
API key, Create a table, and Data pipeline — all code
validated against a live server via
make docs. - New "Streaming large transfers" section in the data resource page
covering file-like uploads, the chunked-transfer caveat for plain
iterables,
poll_download_to, and the sync/async input-type asymmetry. authentication.mdand the Manage namespaces & tables guide were rendering empty code blocks — their snippet scripts had been deleted in an earlier docs consolidation; the scripts are restored and validated.upload/submit_uploaddocstrings and docs now state that upload is the initial load only (the server rejects it with 409 once the table has ever ingested data, even aftertruncate), and thattruncateis processed asynchronously.
0.3.1 - 2026-06-11¶
Fixed¶
- S3 transfers for
data.upload/update/overwrite/upsert/append/query(parquet/csv) now reuse the configured client with a 600 s transfer timeout instead of a short-lived client with httpx's 5 s default, fixinghttpx.WriteTimeouton large payloads. TheAuthorizationheader is stripped from these requests since presigned S3 URLs are self-authenticating. Ineepy/AsyncIneepynow configure a 60 s (10 s connect) default timeout on the main API client instead of httpx's 5 s default, fixing spurioushttpx.ReadTimeouton slower endpoints such astables.delete.
0.3.0 - 2026-06-04¶
Changed¶
data.submit_update/data.updatenow correctly sendsmerge_modein the request body (wasupdate_mode, which the server silently ignored causing every upsert to behave as an overwrite). Callers pass the sameupdate_modeparameter — no API change.data.update(..., update_mode='append')now routes toPATCH /tables/{id}/dataas required by ineep_lakehouse ≥ 0.1.14 (the server removedappendsupport fromPUT). Transparent to callers.- Default client timeout raised from 300 s to 600 s to match the server-side write timeout introduced in ineep_lakehouse v0.1.13.
namespaces.list()namespace_idis now optional. Omit it to list root-level namespaces viaGET /namespaces; existing calls that pass a namespace ID are unaffected.etagis now optional innamespaces.update(),tables.update_metadata(),tables.update_schema(), andusers.update(). When omitted the current ETag is fetched automatically with a HEAD request before the write. Existing callers that supply an explicit ETag continue to work without change.
Added¶
data.submit_overwrite(table_id, data, format)/data.overwrite(table_id, data, format)— explicit overwrite shorthand.data.submit_upsert(table_id, data, format, key_columns)/data.upsert(table_id, data, format, key_columns)— explicit upsert shorthand.data.submit_append(table_id, data, format)/data.append(table_id, data, format)— explicit append viaPATCH /tables/{id}/data(non-deduplicating row insert).namespaces.get_etag(namespace_id)— fetch only the ETag viaHEAD /namespaces/{id}/metadata(no body transfer).tables.get_etag(table_id)— fetch only the ETag viaHEAD /tables/{id}/metadata.users.get_etag(user_id)— fetch only the ETag viaHEAD /users/{id}.
0.2.0 - 2026-06-01¶
Changed¶
⚠ BREAKING — data.query(format='json') now returns QueryResponse¶
The sync query endpoint (GET /tables/{table_id}/data/query) now returns a
pagination envelope. To expose total row count and has_next to callers,
query(format='json') now returns QueryResponse instead of a bare list.
Migration: Replace for row in client.data.query(...) with
for row in client.data.query(...).items. Read
result.metadata.total_count for the total row count after filtering.
New limit and offset parameters are also available for pagination (max
limit=10_000).
data.query(format='parquet'|'csv')now routes throughPOST /data/queriesinternally. The method signature andbytesreturn type are unchanged.data.submit_query()now usesPOST /data/queriesinternally. The signature(table_id, *, filters, format)is unchanged; simplefield:operator:valuefilter strings are automatically converted to the structuredFilterNodepredicate the new endpoint requires.
Added¶
data.submit_cross_query(request: CrossQueryRequest) -> str— submit a full async cross-table query job and return its job ID.data.cross_query(request: CrossQueryRequest) -> bytes— blocking high-level wrapper that submits and polls until bytes are ready.CrossQueryRequest,JoinSpec,FilterLeaf,FilterGroup,FilterNodemodels (mirroringPOST /data/queriesserver schema).QueryResponsemodel with.items: listand.metadata: PaginationMetadata.DataJobType.QUERYenum value — jobs created viaPOST /data/queriesreturnjob_type: 'query';get_job()now accepts these without a validation error.- All new models are exported from the top-level
ineepypackage. data.query(format='json')populatesQueryResponse.etagfrom the server'sETagresponse header (the table's current snapshot ID). Pass it back asif_none_match=result.etagon a subsequent call to make a conditional request: the method returnsNonewhen the server responds with304 Not Modified, allowing callers to skip re-processing when the data has not changed.
0.1.1 - 2026-05-28¶
Changed¶
- Upload endpoints (
submit_upload,submit_update) now expect202 Acceptedwith a JSON body (job_id,upload_url) from the server instead of a307 Temporary RedirectwithX-Job-Id/Locationheaders. - Upload format is now sent as a
?format=query parameter instead of anAcceptheader. - Query endpoint (
submit_query,query) switched fromPOSTwith a JSON body toGETwith filters as repeatable?filters=field:op:valuequery parameters; format selection still usesPrefer: format=<fmt>(RFC 7240). delete_rowsnow issuesDELETE /tables/{id}/data/rowsinstead ofPUT.POST /namespaces/{id}andPOST /tables/{id}now return201 Created; handled transparently — no caller changes required.
No public API changes — all method signatures, return types, and exceptions are unchanged. These are internal HTTP protocol adaptations to track breaking changes in
ineep_lakehouse(unreleased).
0.1.0 - 2026-05-20¶
Added¶
- Initial public release
- Synchronous (
Ineepy) and asynchronous (AsyncIneepy) clients - Full support for users, API keys, namespaces, tables, and data resources
- Token-based authentication with automatic credential persistence
- Optimistic locking with ETags
- Async job polling for long-running data operations
- Comprehensive type hints and Pydantic models