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
20 changes: 12 additions & 8 deletions .openapi-generator-templates/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,28 @@ Two Hotdata-specific DX tweaks applied to the Python `configuration.mustache`:
with `api_key`. Internal `auth_settings()` is patched to read
`self.api_key`.

2. **`workspace_id` and `session_id` are first-class kwargs and
attributes.** Stock openapi-generator exposes apiKey security schemes
only via an opaque `api_key: Dict[str, str]` dict keyed by scheme name.
That's a footgun — callers have to know the generator's
`apiKey`-security machinery to set a workspace scope. The template
adds typed `workspace_id` / `session_id` kwargs (and properties) that
store into a renamed `self.api_keys` dict.
2. **`workspace_id` is a first-class kwarg and attribute.** Stock
openapi-generator exposes apiKey security schemes only via an opaque
`api_key: Dict[str, str]` dict keyed by scheme name. That's a footgun
— callers have to know the generator's `apiKey`-security machinery to
set a workspace scope. The template adds a typed `workspace_id` kwarg
(and property) that stores into a renamed `self.api_keys` dict.

Net caller DX:

```python
cfg = hotdata.Configuration(
api_key="sk_live_...",
workspace_id="ws_abc",
session_id="sb_xyz",
)
```

Because these kwargs are hand-added, they don't disappear when a security
scheme leaves the spec — `auth_settings()` and the per-operation
`_auth_settings` lists do, leaving the kwarg behind as a silent no-op. When
a scheme is dropped upstream, drop its kwarg/property here in the same
change. `tests/test_config_auth_surface.py` fails if the two drift apart.

## Drift tripwire

The regenerate workflow runs `import hotdata` and relies on
Expand Down
31 changes: 4 additions & 27 deletions .openapi-generator-templates/configuration.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,8 @@ class Configuration:
:param api_key: Hotdata API key, sent as `Authorization: Bearer <key>`.
:param workspace_id: Public id of the target workspace, sent as
`X-Workspace-Id`.
:param session_id: Public id of an active sandbox, sent as `X-Session-Id`.
Scopes reads and writes to the sandbox for per-run isolation. Obtain
one by calling `SandboxesApi.create_sandbox`.
:param api_keys: Escape hatch for raw apiKey-scheme values, keyed by
security scheme name. Prefer `workspace_id` / `session_id`.
security scheme name. Prefer `workspace_id`.
:param api_key_prefix: Dict to store API prefix (e.g. Bearer).
The dict key is the name of the security scheme in the OAS specification.
The dict value is an API key prefix when generating the auth data.
Expand Down Expand Up @@ -204,12 +201,11 @@ class Configuration:
:Example:
{{#hasApiKeyMethods}}

Workspace / sandbox scoping example:
Workspace scoping example:

conf = {{packageName}}.Configuration(
api_key='sk_live_...',
workspace_id='ws_abc',
session_id='sb_xyz',
)
{{/hasApiKeyMethods}}
{{#hasHttpBasicMethods}}
Expand Down Expand Up @@ -281,7 +277,6 @@ conf = {{packageName}}.Configuration(
host: Optional[str]=None,
api_key: Optional[str]=None,
workspace_id: Optional[str]=None,
session_id: Optional[str]=None,
api_keys: Optional[Dict[str, str]]=None,
api_key_prefix: Optional[Dict[str, str]]=None,
username: Optional[str]=None,
Expand Down Expand Up @@ -328,13 +323,11 @@ conf = {{packageName}}.Configuration(
from {{packageName}}._auth import _TokenManager
self._token_manager = _TokenManager(api_key, self) if api_key is not None else None
"""Hotdata API key, sent as `Authorization: Bearer <key>`."""
# apiKey-security values (X-Workspace-Id, X-Session-Id), keyed by
# scheme name. Read by the generated `auth_settings()` below.
# apiKey-security values (X-Workspace-Id), keyed by scheme name. Read by
# the generated `auth_settings()` below.
self.api_keys = dict(api_keys) if api_keys else {}
if workspace_id is not None:
self.api_keys["WorkspaceId"] = workspace_id
if session_id is not None:
self.api_keys["SessionId"] = session_id
self.api_key_prefix = {}
if api_key_prefix:
self.api_key_prefix = api_key_prefix
Expand Down Expand Up @@ -659,22 +652,6 @@ conf = {{packageName}}.Configuration(
else:
self.api_keys["WorkspaceId"] = value

@property
def session_id(self) -> Optional[str]:
"""Public id of the active sandbox (sent as `X-Session-Id`).

Scopes reads and writes to the sandbox for per-run isolation.
Obtain a sandbox id by calling `SandboxesApi.create_sandbox`.
"""
return self.api_keys.get("SessionId")

@session_id.setter
def session_id(self, value: Optional[str]) -> None:
if value is None:
self.api_keys.pop("SessionId", None)
else:
self.api_keys["SessionId"] = value

def get_basic_auth_token(self) -> Optional[str]:
"""Gets HTTP basic authentication header (string).

Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

- **Breaking:** sandbox session scoping is gone. The `SessionId` security scheme
no longer exists in the Hotdata OpenAPI spec, so `X-Session-Id` is no longer
sent on `/v1/query` or the results endpoints. `Configuration`'s `session_id`
keyword argument and property are removed along with it — passing
`session_id=` now raises `TypeError` instead of silently setting a header
nothing reads. The server stopped enforcing session scoping before this
release, so requests behave the same with or without the value; drop it from
your `Configuration(...)` calls.
- The legacy `POST /v1/files` upload endpoints have been removed in favor of the
presigned upload flow. This drops `hotdata.UploadsApi.upload_stream`, the raw
`upload_file(body=...)` / `list_uploads` operations, and the `UploadResponse`,
Expand Down
9 changes: 1 addition & 8 deletions docs/QueryApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ Set `async: true` to execute asynchronously — returns a query run ID for polli
### Example

* Api Key Authentication (WorkspaceId):
* Api Key Authentication (SessionId):
* Bearer Authentication (BearerAuth):

```python
Expand All @@ -46,12 +45,6 @@ configuration.api_key['WorkspaceId'] = os.environ["API_KEY"]
# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['WorkspaceId'] = 'Bearer'

# Configure API key authorization: SessionId
configuration.api_key['SessionId'] = os.environ["API_KEY"]

# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['SessionId'] = 'Bearer'

# Configure Bearer authorization: BearerAuth
configuration = hotdata.Configuration(
access_token = os.environ["BEARER_TOKEN"]
Expand Down Expand Up @@ -89,7 +82,7 @@ Name | Type | Description | Notes

### Authorization

[WorkspaceId](../README.md#WorkspaceId), [SessionId](../README.md#SessionId), [BearerAuth](../README.md#BearerAuth)
[WorkspaceId](../README.md#WorkspaceId), [BearerAuth](../README.md#BearerAuth)

### HTTP request headers

Expand Down
18 changes: 2 additions & 16 deletions docs/ResultsApi.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ IEEE special floats (`±Inf`, `NaN`) have no canonical JSON representation. For
### Example

* Api Key Authentication (WorkspaceId):
* Api Key Authentication (SessionId):
* Bearer Authentication (BearerAuth):

```python
Expand All @@ -62,12 +61,6 @@ configuration.api_key['WorkspaceId'] = os.environ["API_KEY"]
# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['WorkspaceId'] = 'Bearer'

# Configure API key authorization: SessionId
configuration.api_key['SessionId'] = os.environ["API_KEY"]

# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['SessionId'] = 'Bearer'

# Configure Bearer authorization: BearerAuth
configuration = hotdata.Configuration(
access_token = os.environ["BEARER_TOKEN"]
Expand Down Expand Up @@ -111,7 +104,7 @@ Name | Type | Description | Notes

### Authorization

[WorkspaceId](../README.md#WorkspaceId), [SessionId](../README.md#SessionId), [BearerAuth](../README.md#BearerAuth)
[WorkspaceId](../README.md#WorkspaceId), [BearerAuth](../README.md#BearerAuth)

### HTTP request headers

Expand Down Expand Up @@ -140,7 +133,6 @@ List stored results for the database named by the required X-Database-Id header.
### Example

* Api Key Authentication (WorkspaceId):
* Api Key Authentication (SessionId):
* Bearer Authentication (BearerAuth):

```python
Expand All @@ -166,12 +158,6 @@ configuration.api_key['WorkspaceId'] = os.environ["API_KEY"]
# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['WorkspaceId'] = 'Bearer'

# Configure API key authorization: SessionId
configuration.api_key['SessionId'] = os.environ["API_KEY"]

# Uncomment below to setup prefix (e.g. Bearer) for API key, if needed
# configuration.api_key_prefix['SessionId'] = 'Bearer'

# Configure Bearer authorization: BearerAuth
configuration = hotdata.Configuration(
access_token = os.environ["BEARER_TOKEN"]
Expand Down Expand Up @@ -211,7 +197,7 @@ Name | Type | Description | Notes

### Authorization

[WorkspaceId](../README.md#WorkspaceId), [SessionId](../README.md#SessionId), [BearerAuth](../README.md#BearerAuth)
[WorkspaceId](../README.md#WorkspaceId), [BearerAuth](../README.md#BearerAuth)

### HTTP request headers

Expand Down
1 change: 0 additions & 1 deletion hotdata/api/query_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,6 @@ def _query_serialize(
# authentication setting
_auth_settings: List[str] = [
'WorkspaceId',
'SessionId',
'BearerAuth'
]

Expand Down
2 changes: 0 additions & 2 deletions hotdata/api/results_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,6 @@ def _get_result_serialize(
# authentication setting
_auth_settings: List[str] = [
'WorkspaceId',
'SessionId',
'BearerAuth'
]

Expand Down Expand Up @@ -667,7 +666,6 @@ def _list_results_serialize(
# authentication setting
_auth_settings: List[str] = [
'WorkspaceId',
'SessionId',
'BearerAuth'
]

Expand Down
41 changes: 4 additions & 37 deletions hotdata/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@
{
"BearerAuth": BearerAuthSetting,
"WorkspaceId": APIKeyAuthSetting,
"SessionId": APIKeyAuthSetting,
},
total=False,
)
Expand Down Expand Up @@ -143,11 +142,8 @@ class Configuration:
:param api_key: Hotdata API key, sent as `Authorization: Bearer <key>`.
:param workspace_id: Public id of the target workspace, sent as
`X-Workspace-Id`.
:param session_id: Public id of an active sandbox, sent as `X-Session-Id`.
Scopes reads and writes to the sandbox for per-run isolation. Obtain
one by calling `SandboxesApi.create_sandbox`.
:param api_keys: Escape hatch for raw apiKey-scheme values, keyed by
security scheme name. Prefer `workspace_id` / `session_id`.
security scheme name. Prefer `workspace_id`.
:param api_key_prefix: Dict to store API prefix (e.g. Bearer).
The dict key is the name of the security scheme in the OAS specification.
The dict value is an API key prefix when generating the auth data.
Expand All @@ -173,12 +169,11 @@ class Configuration:

:Example:

Workspace / sandbox scoping example:
Workspace scoping example:

conf = hotdata.Configuration(
api_key='sk_live_...',
workspace_id='ws_abc',
session_id='sb_xyz',
)
"""

Expand All @@ -189,7 +184,6 @@ def __init__(
host: Optional[str]=None,
api_key: Optional[str]=None,
workspace_id: Optional[str]=None,
session_id: Optional[str]=None,
api_keys: Optional[Dict[str, str]]=None,
api_key_prefix: Optional[Dict[str, str]]=None,
username: Optional[str]=None,
Expand Down Expand Up @@ -233,13 +227,11 @@ def __init__(
from hotdata._auth import _TokenManager
self._token_manager = _TokenManager(api_key, self) if api_key is not None else None
"""Hotdata API key, sent as `Authorization: Bearer <key>`."""
# apiKey-security values (X-Workspace-Id, X-Session-Id), keyed by
# scheme name. Read by the generated `auth_settings()` below.
# apiKey-security values (X-Workspace-Id), keyed by scheme name. Read by
# the generated `auth_settings()` below.
self.api_keys = dict(api_keys) if api_keys else {}
if workspace_id is not None:
self.api_keys["WorkspaceId"] = workspace_id
if session_id is not None:
self.api_keys["SessionId"] = session_id
self.api_key_prefix = {}
if api_key_prefix:
self.api_key_prefix = api_key_prefix
Expand Down Expand Up @@ -541,22 +533,6 @@ def workspace_id(self, value: Optional[str]) -> None:
else:
self.api_keys["WorkspaceId"] = value

@property
def session_id(self) -> Optional[str]:
"""Public id of the active sandbox (sent as `X-Session-Id`).

Scopes reads and writes to the sandbox for per-run isolation.
Obtain a sandbox id by calling `SandboxesApi.create_sandbox`.
"""
return self.api_keys.get("SessionId")

@session_id.setter
def session_id(self, value: Optional[str]) -> None:
if value is None:
self.api_keys.pop("SessionId", None)
else:
self.api_keys["SessionId"] = value

def get_basic_auth_token(self) -> Optional[str]:
"""Gets HTTP basic authentication header (string).

Expand Down Expand Up @@ -599,15 +575,6 @@ def auth_settings(self)-> AuthSettings:
'WorkspaceId',
),
}
if 'SessionId' in self.api_keys:
auth['SessionId'] = {
'type': 'api_key',
'in': 'header',
'key': 'X-Session-Id',
'value': self.get_api_key_with_prefix(
'SessionId',
),
}
return auth

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the SessionId scheme here silently breaks the hand-written session_id surface that this SDK's custom templates add on top of the generated code.

After this change:

  • Configuration.__init__ still accepts session_id= and still does self.api_keys["SessionId"] = session_id (line 240-241)
  • the session_id property/setter still exist (lines 543-557) and still document "sent as X-Session-Id"
  • the class docstring still advertises it (line 145-147) as a workspace/sandbox scoping example

…but nothing consumes it anymore. auth_settings() no longer emits a SessionId entry, and _auth_settings in query_api.py / results_api.py no longer lists it, so update_params_for_auth (api_client.py:649-651) never finds a setting to apply. X-Session-Id is never sent.

That is a silent failure mode, not a loud one: existing callers doing

conf = hotdata.Configuration(api_key=..., workspace_id="ws_abc", session_id="sb_xyz")

get no error, no warning, and conf.session_id still reads back "sb_xyz" — but their queries and result reads now run unscoped against the workspace instead of isolated in the sandbox. Sandbox isolation quietly disappears.

This needs to be resolved one of two ways:

  1. If dropping SessionId is intended — remove the session_id kwarg, property, setter, and docstring references from .openapi-generator-templates/configuration.mustache (lines 166-170, 212, 284, 331-337, 663-676) so the regenerated configuration.py no longer exposes a dead parameter, and note the removal as a breaking change in the CHANGELOG rather than under ### Changed.
  2. If SessionId is still a real header — restore it in the upstream spec so the generator re-emits both the auth_settings() entry and the per-operation _auth_settings membership.

Separately worth confirming while you're here: the session_id docstring points users at SandboxesApi.create_sandbox, but there is no sandboxes_api.py in hotdata/api/. That predates this PR, but it suggests option 1 is the intent — in which case the template cleanup belongs in this change so the public API and the wire behavior stay in sync.


def to_debug_report(self) -> str:
Expand Down
Loading