Skip to content

Commit dbe3b0b

Browse files
feat(loads): add key parameter to load requests (#142)
Co-authored-by: hotdata-automation[bot] <267177015+hotdata-automation[bot]@users.noreply.github.com>
1 parent d302bc6 commit dbe3b0b

4 files changed

Lines changed: 13 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Changed
1111

12+
- feat(loads): add key parameter to load requests
1213
- feat(databases): add fork endpoint
1314

1415
## [0.7.0] - 2026-07-14

docs/LoadManagedTableRequest.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Name | Type | Description | Notes
99
**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]
1010
**async_after_ms** | **int** | If set (requires &#x60;async&#x60; &#x3D; 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 &#x60;async&#x60; &#x3D; true, is rejected with 400. | [optional]
1111
**format** | **str** | File format of the upload: &#x60;\&quot;csv\&quot;&#x60;, &#x60;\&quot;json\&quot;&#x60;, or &#x60;\&quot;parquet\&quot;&#x60;. Optional — when omitted, the format is auto-detected from the upload&#39;s &#x60;Content-Type&#x60; and, failing that, from the file contents. Provide it explicitly to override detection or when the contents are ambiguous. &#x60;\&quot;json\&quot;&#x60; expects newline-delimited JSON (one object per line), not a JSON array. Only applies to &#x60;upload_id&#x60;; query results are always parquet. | [optional]
12+
**key** | **List[str]** | Key columns identifying rows for &#x60;\&quot;delete\&quot;&#x60;, &#x60;\&quot;update\&quot;&#x60;, and &#x60;\&quot;upsert\&quot;&#x60; 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 &#x60;\&quot;replace\&quot;&#x60; and &#x60;\&quot;append\&quot;&#x60;. | [optional]
1213
**mode** | **str** | How the data is applied: &#x60;\&quot;replace\&quot;&#x60; overwrites the table&#39;s contents, &#x60;\&quot;append\&quot;&#x60; inserts the new rows on top of the existing data. |
1314
**result_id** | **str** | ID of a persisted query result (see &#x60;GET /v1/results/{result_id}&#x60;) to publish as the table&#39;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 &#x60;upload_id&#x60;, not both. | [optional]
1415
**upload_id** | **str** | ID of a previously-staged upload (see &#x60;POST /v1/files&#x60;). The upload is claimed atomically; concurrent loads against the same &#x60;upload_id&#x60; return 409. Provide either this or &#x60;result_id&#x60;, not both. | [optional]

hotdata/models/load_managed_table_request.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,11 @@ class LoadManagedTableRequest(BaseModel):
3131
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")
3232
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.")
3333
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.")
34+
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\"`.")
3435
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.")
3536
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.")
3637
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.")
37-
__properties: ClassVar[List[str]] = ["async", "async_after_ms", "format", "mode", "result_id", "upload_id"]
38+
__properties: ClassVar[List[str]] = ["async", "async_after_ms", "format", "key", "mode", "result_id", "upload_id"]
3839

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

89+
# set to None if key (nullable) is None
90+
# and model_fields_set contains the field
91+
if self.key is None and "key" in self.model_fields_set:
92+
_dict['key'] = None
93+
8894
# set to None if result_id (nullable) is None
8995
# and model_fields_set contains the field
9096
if self.result_id is None and "result_id" in self.model_fields_set:
@@ -110,6 +116,7 @@ def from_dict(cls, obj: Optional[Dict[str, Any]]) -> Optional[Self]:
110116
"async": obj.get("async"),
111117
"async_after_ms": obj.get("async_after_ms"),
112118
"format": obj.get("format"),
119+
"key": obj.get("key"),
113120
"mode": obj.get("mode"),
114121
"result_id": obj.get("result_id"),
115122
"upload_id": obj.get("upload_id")

test/test_load_managed_table_request.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def make_instance(self, include_optional) -> LoadManagedTableRequest:
3939
var_async = True,
4040
async_after_ms = 1000,
4141
format = '',
42+
key = [
43+
''
44+
],
4245
mode = '',
4346
result_id = '',
4447
upload_id = ''

0 commit comments

Comments
 (0)