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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- feat(loads): add key parameter to load requests
- feat(databases): add fork endpoint

## [0.7.0] - 2026-07-14
Expand Down
1 change: 1 addition & 0 deletions docs/LoadManagedTableRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Name | Type | Description | Notes
**var_async** | **bool** | When true, run the load as a background job and return a job ID to poll instead of blocking until it finishes. Recommended for large uploads, which can take longer than an HTTP request should stay open. | [optional]
**async_after_ms** | **int** | If set (requires `async` = true), wait up to this many milliseconds for the load to finish: if it completes in time the full result is returned (200), otherwise a 202 with a job ID to poll. Must be between 1000 and the server maximum; a value out of that range, or set without `async` = true, is rejected with 400. | [optional]
**format** | **str** | File format of the upload: `\"csv\"`, `\"json\"`, or `\"parquet\"`. Optional — when omitted, the format is auto-detected from the upload's `Content-Type` and, failing that, from the file contents. Provide it explicitly to override detection or when the contents are ambiguous. `\"json\"` expects newline-delimited JSON (one object per line), not a JSON array. Only applies to `upload_id`; query results are always parquet. | [optional]
**key** | **List[str]** | Key columns identifying rows for `\"delete\"`, `\"update\"`, and `\"upsert\"` loads — the columns whose values decide which existing row an incoming row removes, updates, or replaces. Omit to use the key the table was created with. Keep the key consistent across loads of the same table: changing it re-targets which rows are matched. Ignored for `\"replace\"` and `\"append\"`. | [optional]
**mode** | **str** | How the data is applied: `\"replace\"` overwrites the table's contents, `\"append\"` inserts the new rows on top of the existing data. |
**result_id** | **str** | ID of a persisted query result (see `GET /v1/results/{result_id}`) to publish as the table's contents. The result is copied into the table, so the table keeps its data even after the result expires. A result can be loaded into any number of tables. Provide either this or `upload_id`, not both. | [optional]
**upload_id** | **str** | ID of a previously-staged upload (see `POST /v1/files`). The upload is claimed atomically; concurrent loads against the same `upload_id` return 409. Provide either this or `result_id`, not both. | [optional]
Expand Down
9 changes: 8 additions & 1 deletion hotdata/models/load_managed_table_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ class LoadManagedTableRequest(BaseModel):
var_async: Optional[StrictBool] = Field(default=None, description="When true, run the load as a background job and return a job ID to poll instead of blocking until it finishes. Recommended for large uploads, which can take longer than an HTTP request should stay open.", alias="async")
async_after_ms: Optional[Annotated[int, Field(strict=True, ge=1000)]] = Field(default=None, description="If set (requires `async` = true), wait up to this many milliseconds for the load to finish: if it completes in time the full result is returned (200), otherwise a 202 with a job ID to poll. Must be between 1000 and the server maximum; a value out of that range, or set without `async` = true, is rejected with 400.")
format: Optional[StrictStr] = Field(default=None, description="File format of the upload: `\"csv\"`, `\"json\"`, or `\"parquet\"`. Optional — when omitted, the format is auto-detected from the upload's `Content-Type` and, failing that, from the file contents. Provide it explicitly to override detection or when the contents are ambiguous. `\"json\"` expects newline-delimited JSON (one object per line), not a JSON array. Only applies to `upload_id`; query results are always parquet.")
key: Optional[List[StrictStr]] = Field(default=None, description="Key columns identifying rows for `\"delete\"`, `\"update\"`, and `\"upsert\"` loads — the columns whose values decide which existing row an incoming row removes, updates, or replaces. Omit to use the key the table was created with. Keep the key consistent across loads of the same table: changing it re-targets which rows are matched. Ignored for `\"replace\"` and `\"append\"`.")
mode: StrictStr = Field(description="How the data is applied: `\"replace\"` overwrites the table's contents, `\"append\"` inserts the new rows on top of the existing data.")
result_id: Optional[StrictStr] = Field(default=None, description="ID of a persisted query result (see `GET /v1/results/{result_id}`) to publish as the table's contents. The result is copied into the table, so the table keeps its data even after the result expires. A result can be loaded into any number of tables. Provide either this or `upload_id`, not both.")
upload_id: Optional[StrictStr] = Field(default=None, description="ID of a previously-staged upload (see `POST /v1/files`). The upload is claimed atomically; concurrent loads against the same `upload_id` return 409. Provide either this or `result_id`, not both.")
__properties: ClassVar[List[str]] = ["async", "async_after_ms", "format", "mode", "result_id", "upload_id"]
__properties: ClassVar[List[str]] = ["async", "async_after_ms", "format", "key", "mode", "result_id", "upload_id"]

model_config = ConfigDict(
populate_by_name=True,
Expand Down Expand Up @@ -85,6 +86,11 @@ def to_dict(self) -> Dict[str, Any]:
if self.format is None and "format" in self.model_fields_set:
_dict['format'] = None

# set to None if key (nullable) is None
# and model_fields_set contains the field
if self.key is None and "key" in self.model_fields_set:
_dict['key'] = None

# set to None if result_id (nullable) is None
# and model_fields_set contains the field
if self.result_id is None and "result_id" in self.model_fields_set:
Expand All @@ -110,6 +116,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
"async": obj.get("async"),
"async_after_ms": obj.get("async_after_ms"),
"format": obj.get("format"),
"key": obj.get("key"),
"mode": obj.get("mode"),
"result_id": obj.get("result_id"),
"upload_id": obj.get("upload_id")
Expand Down
3 changes: 3 additions & 0 deletions test/test_load_managed_table_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ def make_instance(self, include_optional) -> LoadManagedTableRequest:
var_async = True,
async_after_ms = 1000,
format = '',
key = [
''
],
mode = '',
result_id = '',
upload_id = ''
Expand Down