databricks-mason 0.1.0.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- databricks_mason/__init__.py +1 -0
- databricks_mason/auth.py +83 -0
- databricks_mason/cli.py +72 -0
- databricks_mason/client.py +298 -0
- databricks_mason/deploy.py +325 -0
- databricks_mason/errors.py +53 -0
- databricks_mason/memory.py +347 -0
- databricks_mason/render.py +185 -0
- databricks_mason/sessions.py +442 -0
- databricks_mason/timefmt.py +85 -0
- databricks_mason/tracing.py +263 -0
- databricks_mason-0.1.0.dev0.dist-info/METADATA +80 -0
- databricks_mason-0.1.0.dev0.dist-info/RECORD +15 -0
- databricks_mason-0.1.0.dev0.dist-info/WHEEL +4 -0
- databricks_mason-0.1.0.dev0.dist-info/entry_points.txt +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Databricks integration for Mason."""
|
databricks_mason/auth.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""`mason login` / `logout` — remember an optional Databricks profile.
|
|
2
|
+
|
|
3
|
+
`login` validates a named profile and persists the selection; the root group falls back
|
|
4
|
+
to it whenever `-p` is omitted. Without a saved profile, the Databricks SDK performs its
|
|
5
|
+
normal default authentication resolution. `logout` removes only Mason's saved selection,
|
|
6
|
+
not the underlying credentials. State lives in a small JSON file under `~/.mason`
|
|
7
|
+
(override the directory with `MASON_CONFIG_HOME`, mainly for tests).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import pathlib
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import click
|
|
18
|
+
|
|
19
|
+
from databricks_mason import render
|
|
20
|
+
from databricks_mason.client import AgentApiClient
|
|
21
|
+
from databricks_mason.errors import AgentCliError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _config_file() -> pathlib.Path:
|
|
25
|
+
base = os.environ.get("MASON_CONFIG_HOME")
|
|
26
|
+
root = pathlib.Path(base) if base else pathlib.Path.home() / ".mason"
|
|
27
|
+
return root / "config.json"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_default_profile() -> Optional[str]:
|
|
31
|
+
"""The profile saved by `mason login`, or None if the user never logged in."""
|
|
32
|
+
try:
|
|
33
|
+
return json.loads(_config_file().read_text()).get("profile")
|
|
34
|
+
except (OSError, json.JSONDecodeError):
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _save_default_profile(profile: str) -> None:
|
|
39
|
+
path = _config_file()
|
|
40
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
41
|
+
path.write_text(json.dumps({"profile": profile}, indent=2) + "\n")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.command()
|
|
45
|
+
@click.option(
|
|
46
|
+
"--profile",
|
|
47
|
+
"-p",
|
|
48
|
+
default=None,
|
|
49
|
+
help="Profile to authenticate with and remember as the default.",
|
|
50
|
+
)
|
|
51
|
+
@click.pass_obj
|
|
52
|
+
def login(obj, profile) -> None:
|
|
53
|
+
"""Validate a profile's credentials and save it as the default, so later commands can omit -p."""
|
|
54
|
+
profile = profile or obj.profile
|
|
55
|
+
if not profile:
|
|
56
|
+
raise AgentCliError(
|
|
57
|
+
"No profile to save.",
|
|
58
|
+
hint="Pass one to remember, e.g. `mason login --profile my-workspace`.",
|
|
59
|
+
)
|
|
60
|
+
client = AgentApiClient(profile)
|
|
61
|
+
user = client.current_user # round-trips current_user.me(), so a bad profile fails here
|
|
62
|
+
_save_default_profile(profile)
|
|
63
|
+
if obj.output == "json":
|
|
64
|
+
render.emit_json({"profile": profile, "user": user, "host": client.host})
|
|
65
|
+
return
|
|
66
|
+
render.success(
|
|
67
|
+
f"Logged in as {user}",
|
|
68
|
+
fields={"Profile": profile, "Host": client.host},
|
|
69
|
+
next_steps=["mason sessions stores list", "mason memory stores list"],
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@click.command()
|
|
74
|
+
@click.pass_obj
|
|
75
|
+
def logout(obj) -> None:
|
|
76
|
+
"""Forget the saved profile selection without deleting its credentials."""
|
|
77
|
+
path = _config_file()
|
|
78
|
+
existed = path.exists()
|
|
79
|
+
path.unlink(missing_ok=True)
|
|
80
|
+
if obj.output == "json":
|
|
81
|
+
render.emit_json({"logged_out": existed})
|
|
82
|
+
return
|
|
83
|
+
render.success("Logged out" if existed else "No saved login to clear")
|
databricks_mason/cli.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""`mason` — the Databricks CLI for agent deployment, memory, and sessions.
|
|
2
|
+
|
|
3
|
+
Root Click group. Global `--profile` and `--output` flow to every subcommand via
|
|
4
|
+
`CliContext` on `ctx.obj`; subcommands build an `AgentApiClient` from it on demand.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
import click
|
|
12
|
+
|
|
13
|
+
from databricks_mason.auth import load_default_profile, login, logout
|
|
14
|
+
from databricks_mason.client import AgentApiClient
|
|
15
|
+
from databricks_mason.deploy import deploy, deployments
|
|
16
|
+
from databricks_mason.memory import memory
|
|
17
|
+
from databricks_mason.sessions import sessions
|
|
18
|
+
from databricks_mason.tracing import tracing
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CliContext:
|
|
22
|
+
"""Shared per-invocation state: selected profile, output mode, lazily-built client."""
|
|
23
|
+
|
|
24
|
+
def __init__(self, profile: Optional[str], output: str):
|
|
25
|
+
self.profile = profile
|
|
26
|
+
self.output = output
|
|
27
|
+
self._client: Optional[AgentApiClient] = None
|
|
28
|
+
|
|
29
|
+
def client(self) -> AgentApiClient:
|
|
30
|
+
if self._client is None:
|
|
31
|
+
self._client = AgentApiClient(self.profile)
|
|
32
|
+
return self._client
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@click.group(context_settings={"help_option_names": ["-h", "--help"]})
|
|
36
|
+
@click.option(
|
|
37
|
+
"--profile", "-p", default=None, help="~/.databrickscfg profile to authenticate with."
|
|
38
|
+
)
|
|
39
|
+
@click.option(
|
|
40
|
+
"--output",
|
|
41
|
+
"-o",
|
|
42
|
+
type=click.Choice(["text", "json"]),
|
|
43
|
+
default="text",
|
|
44
|
+
help="Output format (default: text).",
|
|
45
|
+
)
|
|
46
|
+
@click.version_option(package_name="databricks-mason", prog_name="mason")
|
|
47
|
+
@click.pass_context
|
|
48
|
+
def mason(ctx: click.Context, profile: Optional[str], output: str) -> None:
|
|
49
|
+
"""Mason: deploy agents and manage their memory and sessions.
|
|
50
|
+
|
|
51
|
+
Targets the agents/v1 preview APIs served on a workspace; auth comes from a
|
|
52
|
+
.databrickscfg profile (pass --profile / -p, run `mason login` to save a default,
|
|
53
|
+
or rely on the SDK's default resolution).
|
|
54
|
+
"""
|
|
55
|
+
ctx.obj = CliContext(profile=profile or load_default_profile(), output=output)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
mason.add_command(login)
|
|
59
|
+
mason.add_command(logout)
|
|
60
|
+
mason.add_command(memory)
|
|
61
|
+
mason.add_command(sessions)
|
|
62
|
+
mason.add_command(tracing)
|
|
63
|
+
mason.add_command(deploy)
|
|
64
|
+
mason.add_command(deployments)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def main() -> None:
|
|
68
|
+
mason(prog_name="mason")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
main()
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
"""Authenticated REST client for the agents/v1 memory and session APIs.
|
|
2
|
+
|
|
3
|
+
Wraps a databricks-sdk `WorkspaceClient` so auth/host come from a `.databrickscfg`
|
|
4
|
+
profile. Each method maps to one API operation.
|
|
5
|
+
Deployment is handled separately (deploy.py) since it wraps the `databricks apps` CLI.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any, Optional
|
|
11
|
+
|
|
12
|
+
from databricks.sdk import WorkspaceClient
|
|
13
|
+
|
|
14
|
+
from databricks_mason.errors import AgentCliError, wrap_api_error
|
|
15
|
+
|
|
16
|
+
_BASE = "/api/agents/v1"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _query(**kwargs: Any) -> dict[str, Any]:
|
|
20
|
+
"""Build a query dict, dropping None and empty values."""
|
|
21
|
+
return {k: v for k, v in kwargs.items() if v is not None and v != ""}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def memory_store_path(name: str) -> str:
|
|
25
|
+
"""Normalize a store id or name into the `memory-stores/{id}` resource segment."""
|
|
26
|
+
name = name.strip().strip("/")
|
|
27
|
+
return name if name.startswith("memory-stores/") else f"memory-stores/{name}"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def memory_entry_path(store: str, entry: str) -> str:
|
|
31
|
+
entry = entry.strip().strip("/")
|
|
32
|
+
if entry.startswith("memory-stores/"):
|
|
33
|
+
return entry
|
|
34
|
+
return f"{memory_store_path(store)}/entries/{entry}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class AgentApiClient:
|
|
38
|
+
"""Thin, authenticated wrapper over the agents/v1 REST surface."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, profile: Optional[str] = None):
|
|
41
|
+
try:
|
|
42
|
+
self._w = WorkspaceClient(profile=profile)
|
|
43
|
+
except Exception as exc: # noqa: BLE001 - surfaced as a clean CLI error
|
|
44
|
+
raise AgentCliError(
|
|
45
|
+
f"Could not initialize Databricks auth: {exc}",
|
|
46
|
+
hint="Check your profile (`databricks auth login --profile <name>`) "
|
|
47
|
+
"or pass --profile.",
|
|
48
|
+
) from exc
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def host(self) -> str:
|
|
52
|
+
return self._w.config.host or "unknown"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def current_user(self) -> str:
|
|
56
|
+
"""The authenticated user's name (used to derive the app source workspace path)."""
|
|
57
|
+
return str(self._w.current_user.me().user_name or "unknown")
|
|
58
|
+
|
|
59
|
+
def _do(
|
|
60
|
+
self, method: str, path: str, *, query: Optional[dict] = None, body: Optional[dict] = None
|
|
61
|
+
) -> Any:
|
|
62
|
+
try:
|
|
63
|
+
return self._w.api_client.do(method, path, query=query, body=body)
|
|
64
|
+
except Exception as exc: # noqa: BLE001 - normalized to AgentCliError
|
|
65
|
+
raise wrap_api_error(exc) from exc
|
|
66
|
+
|
|
67
|
+
# --- memory stores -------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
def create_memory_store(self, display_name: str, description: Optional[str] = None) -> dict:
|
|
70
|
+
body = _query(display_name=display_name, description=description)
|
|
71
|
+
return self._do("POST", f"{_BASE}/memory-stores", body=body)
|
|
72
|
+
|
|
73
|
+
def get_memory_store(self, name: str) -> dict:
|
|
74
|
+
return self._do("GET", f"{_BASE}/{memory_store_path(name)}")
|
|
75
|
+
|
|
76
|
+
def list_memory_stores(
|
|
77
|
+
self, page_size: Optional[int] = None, page_token: Optional[str] = None
|
|
78
|
+
) -> dict:
|
|
79
|
+
return self._do(
|
|
80
|
+
"GET",
|
|
81
|
+
f"{_BASE}/memory-stores",
|
|
82
|
+
query=_query(page_size=page_size, page_token=page_token),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def update_memory_store(
|
|
86
|
+
self, name: str, display_name: Optional[str] = None, description: Optional[str] = None
|
|
87
|
+
) -> dict:
|
|
88
|
+
body = _query(display_name=display_name, description=description)
|
|
89
|
+
mask = ",".join(body.keys())
|
|
90
|
+
return self._do(
|
|
91
|
+
"PATCH", f"{_BASE}/{memory_store_path(name)}", query=_query(update_mask=mask), body=body
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def delete_memory_store(self, name: str) -> dict:
|
|
95
|
+
return self._do("DELETE", f"{_BASE}/{memory_store_path(name)}")
|
|
96
|
+
|
|
97
|
+
# --- memory entries ------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
def create_memory_entry(
|
|
100
|
+
self,
|
|
101
|
+
store: str,
|
|
102
|
+
actor_id: str,
|
|
103
|
+
path: str,
|
|
104
|
+
content: Optional[str] = None,
|
|
105
|
+
description: Optional[str] = None,
|
|
106
|
+
session_id: Optional[str] = None,
|
|
107
|
+
source_type: Optional[str] = None,
|
|
108
|
+
) -> dict:
|
|
109
|
+
body = _query(
|
|
110
|
+
actor_id=actor_id,
|
|
111
|
+
path=path,
|
|
112
|
+
content=content,
|
|
113
|
+
description=description,
|
|
114
|
+
session_id=session_id,
|
|
115
|
+
source_type=source_type,
|
|
116
|
+
)
|
|
117
|
+
return self._do("POST", f"{_BASE}/{memory_store_path(store)}/entries", body=body)
|
|
118
|
+
|
|
119
|
+
def get_memory_entry(self, store: str, entry: str) -> dict:
|
|
120
|
+
return self._do("GET", f"{_BASE}/{memory_entry_path(store, entry)}")
|
|
121
|
+
|
|
122
|
+
def list_memory_entries(
|
|
123
|
+
self,
|
|
124
|
+
store: str,
|
|
125
|
+
actor_id: str,
|
|
126
|
+
path_prefix: Optional[str] = None,
|
|
127
|
+
session_id: Optional[str] = None,
|
|
128
|
+
page_size: Optional[int] = None,
|
|
129
|
+
page_token: Optional[str] = None,
|
|
130
|
+
) -> dict:
|
|
131
|
+
return self._do(
|
|
132
|
+
"GET",
|
|
133
|
+
f"{_BASE}/{memory_store_path(store)}/entries",
|
|
134
|
+
query=_query(
|
|
135
|
+
actor_id=actor_id,
|
|
136
|
+
path_prefix=path_prefix,
|
|
137
|
+
session_id=session_id,
|
|
138
|
+
page_size=page_size,
|
|
139
|
+
page_token=page_token,
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def search_memory_entries(
|
|
144
|
+
self, store: str, actor_id: str, query: str, limit: Optional[int] = None
|
|
145
|
+
) -> dict:
|
|
146
|
+
body = _query(actor_id=actor_id, query=query, limit=limit)
|
|
147
|
+
return self._do("POST", f"{_BASE}/{memory_store_path(store)}/entries:search", body=body)
|
|
148
|
+
|
|
149
|
+
def update_memory_entry(
|
|
150
|
+
self,
|
|
151
|
+
store: str,
|
|
152
|
+
entry: str,
|
|
153
|
+
content: Optional[str] = None,
|
|
154
|
+
description: Optional[str] = None,
|
|
155
|
+
) -> dict:
|
|
156
|
+
body = _query(content=content, description=description)
|
|
157
|
+
return self._do("PATCH", f"{_BASE}/{memory_entry_path(store, entry)}", body=body)
|
|
158
|
+
|
|
159
|
+
def delete_memory_entry(self, store: str, entry: str) -> dict:
|
|
160
|
+
return self._do("DELETE", f"{_BASE}/{memory_entry_path(store, entry)}")
|
|
161
|
+
|
|
162
|
+
# --- session stores ------------------------------------------------------
|
|
163
|
+
|
|
164
|
+
def create_session_store(
|
|
165
|
+
self, name: str, description: Optional[str] = None, metadata: Optional[dict] = None
|
|
166
|
+
) -> dict:
|
|
167
|
+
body = _query(description=description, metadata=metadata)
|
|
168
|
+
return self._do(
|
|
169
|
+
"POST", f"{_BASE}/session-stores", query={"session_store_name": name}, body=body
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def get_session_store(self, name: str) -> dict:
|
|
173
|
+
return self._do("GET", f"{_BASE}/session-stores/{name}")
|
|
174
|
+
|
|
175
|
+
def list_session_stores(
|
|
176
|
+
self, page_size: Optional[int] = None, page_token: Optional[str] = None
|
|
177
|
+
) -> dict:
|
|
178
|
+
return self._do(
|
|
179
|
+
"GET",
|
|
180
|
+
f"{_BASE}/session-stores",
|
|
181
|
+
query=_query(page_size=page_size, page_token=page_token),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def update_session_store(
|
|
185
|
+
self, name: str, description: Optional[str] = None, metadata: Optional[dict] = None
|
|
186
|
+
) -> dict:
|
|
187
|
+
body = _query(description=description, metadata=metadata)
|
|
188
|
+
mask = ",".join(body.keys())
|
|
189
|
+
return self._do(
|
|
190
|
+
"PATCH", f"{_BASE}/session-stores/{name}", query=_query(update_mask=mask), body=body
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
def delete_session_store(self, name: str) -> dict:
|
|
194
|
+
return self._do("DELETE", f"{_BASE}/session-stores/{name}")
|
|
195
|
+
|
|
196
|
+
# --- sessions ------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
def create_session(
|
|
199
|
+
self,
|
|
200
|
+
store: str,
|
|
201
|
+
actor_id: str,
|
|
202
|
+
session_id: Optional[str] = None,
|
|
203
|
+
parent_session_id: Optional[str] = None,
|
|
204
|
+
metadata: Optional[dict] = None,
|
|
205
|
+
) -> dict:
|
|
206
|
+
body = _query(actor_id=actor_id, parent_session_id=parent_session_id, metadata=metadata)
|
|
207
|
+
return self._do(
|
|
208
|
+
"POST",
|
|
209
|
+
f"{_BASE}/session-stores/{store}/sessions",
|
|
210
|
+
query=_query(session_id=session_id),
|
|
211
|
+
body=body,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def list_sessions(
|
|
215
|
+
self,
|
|
216
|
+
store: str,
|
|
217
|
+
filter: Optional[str] = None,
|
|
218
|
+
order_by: Optional[str] = None,
|
|
219
|
+
page_size: Optional[int] = None,
|
|
220
|
+
page_token: Optional[str] = None,
|
|
221
|
+
) -> dict:
|
|
222
|
+
return self._do(
|
|
223
|
+
"GET",
|
|
224
|
+
f"{_BASE}/session-stores/{store}/sessions",
|
|
225
|
+
query=_query(
|
|
226
|
+
filter=filter, order_by=order_by, page_size=page_size, page_token=page_token
|
|
227
|
+
),
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
def get_session(self, session_id: str, store: Optional[str] = None) -> dict:
|
|
231
|
+
if store:
|
|
232
|
+
return self._do("GET", f"{_BASE}/session-stores/{store}/sessions/{session_id}")
|
|
233
|
+
return self._do("GET", f"{_BASE}/sessions/{session_id}")
|
|
234
|
+
|
|
235
|
+
def update_session(self, store: str, session_id: str, metadata: dict) -> dict:
|
|
236
|
+
return self._do(
|
|
237
|
+
"PATCH",
|
|
238
|
+
f"{_BASE}/session-stores/{store}/sessions/{session_id}",
|
|
239
|
+
query={"update_mask": "metadata"},
|
|
240
|
+
body=_query(metadata=metadata),
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
def delete_session(self, store: str, session_id: str, force: bool = False) -> dict:
|
|
244
|
+
return self._do(
|
|
245
|
+
"DELETE",
|
|
246
|
+
f"{_BASE}/session-stores/{store}/sessions/{session_id}",
|
|
247
|
+
query=_query(force=force or None),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def fork_session(
|
|
251
|
+
self,
|
|
252
|
+
store: str,
|
|
253
|
+
source_session_id: str,
|
|
254
|
+
actor_id: str,
|
|
255
|
+
up_to_item_id: Optional[str] = None,
|
|
256
|
+
session_id: Optional[str] = None,
|
|
257
|
+
metadata: Optional[dict] = None,
|
|
258
|
+
) -> dict:
|
|
259
|
+
body = _query(
|
|
260
|
+
source_session_id=source_session_id,
|
|
261
|
+
actor_id=actor_id,
|
|
262
|
+
up_to_item_id=up_to_item_id,
|
|
263
|
+
session_id=session_id,
|
|
264
|
+
metadata=metadata,
|
|
265
|
+
)
|
|
266
|
+
return self._do("POST", f"{_BASE}/session-stores/{store}/sessions:fork", body=body)
|
|
267
|
+
|
|
268
|
+
# --- session items -------------------------------------------------------
|
|
269
|
+
|
|
270
|
+
def list_session_items(
|
|
271
|
+
self,
|
|
272
|
+
store: str,
|
|
273
|
+
session_id: str,
|
|
274
|
+
order_by: Optional[str] = None,
|
|
275
|
+
page_size: Optional[int] = None,
|
|
276
|
+
page_token: Optional[str] = None,
|
|
277
|
+
) -> dict:
|
|
278
|
+
return self._do(
|
|
279
|
+
"GET",
|
|
280
|
+
f"{_BASE}/session-stores/{store}/sessions/{session_id}/items",
|
|
281
|
+
query=_query(order_by=order_by, page_size=page_size, page_token=page_token),
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
def append_session_items(self, store: str, session_id: str, items: list[dict]) -> dict:
|
|
285
|
+
body = {"items": [{"data": item} for item in items]}
|
|
286
|
+
return self._do(
|
|
287
|
+
"POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:append", body=body
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
def pop_session_item(self, store: str, session_id: str) -> dict:
|
|
291
|
+
return self._do(
|
|
292
|
+
"POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:pop", body={}
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
def clear_session_items(self, store: str, session_id: str) -> dict:
|
|
296
|
+
return self._do(
|
|
297
|
+
"POST", f"{_BASE}/session-stores/{store}/sessions/{session_id}/items:clear", body={}
|
|
298
|
+
)
|