datarobot_genai.drmcputils.panels.store
store
Server-side panel store.
Persists panels through a :class:~datarobot_genai.drmcputils.files.store.BlobStore:
each panel is a small JSON manifest blob (the panel metadata) plus an
optional separate payload blob for bulky content (a Dataset's Parquet, a
Chart's spec). Panel ids are client-generated (uuid4().hex) and embedded in
the blob path, so a panel keeps its id across moves.
Layout. All panels live in one shared Files container under
<source>/<scope>/ paths, where scope is the normalized conversation id
(_shared for unscoped consumers)::
panels <- the shared container (one registry row)
├── staging/
│ └── <conversation_id>/
│ ├── <panel_id>.json (manifest)
│ └── <panel_id>.payload (optional payload blob)
└── main/
└── _shared/
└── <panel_id>.json
Source-first ordering keeps every listing a single server-side prefix query: a
conversation-scoped store lists <source>/<conversation_id>/ and an unscoped
store keeps the legacy global view via <source>/.
Conversation scoping. A store may be scoped to a conversation
(PanelStore(blobs, conversation_id=...)); the shared store factory resolves
the id per request from the x-datarobot-conversation-id header (the same
header the previous panel-library MCP server used). Conversation ids are
normalized to [0-9A-Za-z_] and capped at 128 chars so they are safe as path
segments. Scoping is enforced on every id-based operation, not just listings:
a scoped store resolves ids only against its own conversation and _shared
(other conversations' panels are invisible — not-found, no existence leak), it
can read _shared panels but not delete or move them, and resolution probes
exact manifest paths (own scope, then _shared, across the hinted + known
sources) so the hot path never lists the whole container. An unscoped store
keeps the global view and may modify anything.
Moves. move promotes a panel between sources (staging→main) by renaming
its blobs' paths in place — no copy, no delete — so the panel id and external
references stay valid. The panel's own conversation scope (recorded on create)
is kept regardless of the mover's scope.
Legacy panels. Panels stored before the shared-container layout (one
standalone Files container per blob, tag-based discovery) stay reachable —
get/get_payload/delete fall back to the blob id when the panel is
not found in the shared container. They no longer appear in listings and
cannot be moved (recreate them instead).
The store depends only on the BlobStore Protocol, so it is backed by the
DataRobot Files API in production and by an in-memory fake in tests.
PanelStore
CRUD + listing for panels over a :class:BlobStore.
conversation_id (optional) scopes the store to one conversation: blobs
it creates are placed under <source>/<conversation_id>/ and list
returns only that conversation's panels. None keeps the legacy
unscoped behavior (blobs under <source>/_shared/, global list view).
Source code in datarobot_genai/drmcputils/panels/store.py
152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 | |
conversation_id
property
The normalized conversation id this store is scoped to (None = unscoped).
create
async
create(panel: BasePanel, *, source: str = DEFAULT_SOURCE, payload: bytes | None = None, payload_name: str | None = None, content_type: str | None = None) -> Panel
Persist panel (and an optional payload blob); returns it with id set.
Source code in datarobot_genai/drmcputils/panels/store.py
get
async
Load a panel by id. Payload is not hydrated here.
A scoped store reads its own conversation's panels plus _shared
ones; other conversations' panels are not found. source is an
optional resolution hint (O(1) direct probe when it matches). Panels
created before conversation scoping existed (legacy standalone blobs)
stay reachable by id.
Source code in datarobot_genai/drmcputils/panels/store.py
list
async
list(*, source: str = DEFAULT_SOURCE, limit: int = DEFAULT_LIST_LIMIT, offset: int = 0) -> list[Panel]
List panels in source (metadata only); page with limit/offset.
A conversation-scoped store lists only its conversation's panels; an unscoped store keeps the legacy global view across conversations.
Source code in datarobot_genai/drmcputils/panels/store.py
move
async
Move a panel to to_source (e.g. promote staging→main), preserving its id.
The move renames the manifest (and payload) blob paths in place — no
copy, no delete — so the panel id and any external references to it
stay valid. The panel's own conversation scope (recorded on create) is
kept regardless of the scope of the store performing the move. A
scoped store may only move its own conversation's panels; source
is an optional resolution hint.
Source code in datarobot_genai/drmcputils/panels/store.py
get_payload
async
Fetch a panel's payload blob bytes (by id or loaded panel); None if it has none.
Source code in datarobot_genai/drmcputils/panels/store.py
delete
async
Delete a panel's manifest and its payload blob (if any).
A scoped store may only delete its own conversation's panels: shared
panels are rejected, other conversations' panels are not found.
source is an optional resolution hint.
Source code in datarobot_genai/drmcputils/panels/store.py
normalize_conversation_id
Normalize a raw conversation id to a path- and tag-safe token.
Every character outside [0-9A-Za-z_] becomes _ and the result is
capped at 128 characters. Returns None for missing/blank ids (an
unscoped store).