langgraph-runtime-inmem 0.34.0.dev4__tar.gz → 0.34.0.dev5__tar.gz

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.
Files changed (18) hide show
  1. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/PKG-INFO +1 -1
  2. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/__init__.py +1 -1
  3. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/checkpoint.py +10 -4
  4. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/ops.py +111 -6
  5. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/pyproject.toml +4 -0
  6. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/.gitignore +0 -0
  7. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/Makefile +0 -0
  8. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/README.md +0 -0
  9. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/_persistence.py +0 -0
  10. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/database.py +0 -0
  11. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/inmem_stream.py +0 -0
  12. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/lifespan.py +0 -0
  13. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/metrics.py +0 -0
  14. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/queue.py +0 -0
  15. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/retry.py +0 -0
  16. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/routes.py +0 -0
  17. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/langgraph_runtime_inmem/store.py +0 -0
  18. {langgraph_runtime_inmem-0.34.0.dev4 → langgraph_runtime_inmem-0.34.0.dev5}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-runtime-inmem
3
- Version: 0.34.0.dev4
3
+ Version: 0.34.0.dev5
4
4
  Summary: Inmem implementation for the LangGraph API server.
5
5
  Author-email: Will Fu-Hinthorn <will@langchain.dev>
6
6
  License: Elastic-2.0
@@ -10,7 +10,7 @@ from langgraph_runtime_inmem import (
10
10
  store,
11
11
  )
12
12
 
13
- __version__ = "0.34.0.dev4"
13
+ __version__ = "0.34.0.dev5"
14
14
  __all__ = [
15
15
  "ops",
16
16
  "database",
@@ -157,7 +157,9 @@ class InMemorySaver(InMemorySaverBase):
157
157
  if os.path.exists(file_path):
158
158
  os.remove(file_path)
159
159
 
160
- async def _decrypt_json(self, data: dict[str, Any]) -> dict[str, Any]:
160
+ async def _decrypt_json(
161
+ self, data: dict[str, Any], model: str, field: str
162
+ ) -> dict[str, Any]:
161
163
  """Decrypt a dict if custom encryption is configured."""
162
164
  from langgraph_api import config as api_config # noqa: PLC0415
163
165
 
@@ -168,7 +170,7 @@ class InMemorySaver(InMemorySaverBase):
168
170
  decrypt_json_if_needed,
169
171
  )
170
172
 
171
- result = await decrypt_json_if_needed(data, get_encryption(), "checkpoint")
173
+ result = await decrypt_json_if_needed(data, get_encryption(), model, field)
172
174
  if result is None:
173
175
  raise ValueError("decrypt_json_if_needed returned None for non-None input")
174
176
  return result
@@ -180,7 +182,9 @@ class InMemorySaver(InMemorySaverBase):
180
182
  return None
181
183
 
182
184
  # Decrypt metadata if encryption is enabled
183
- decrypted_metadata = await self._decrypt_json(tuple_.metadata)
185
+ decrypted_metadata = await self._decrypt_json(
186
+ tuple_.metadata, "run", "metadata"
187
+ )
184
188
 
185
189
  from langgraph.checkpoint.base import ( # noqa: PLC0415
186
190
  CheckpointTuple as CPTuple,
@@ -209,7 +213,9 @@ class InMemorySaver(InMemorySaverBase):
209
213
 
210
214
  for tuple_ in self.list(config, filter=filter, before=before, limit=limit):
211
215
  # Decrypt metadata if encryption is enabled
212
- decrypted_metadata = await self._decrypt_json(tuple_.metadata)
216
+ decrypted_metadata = await self._decrypt_json(
217
+ tuple_.metadata, "run", "metadata"
218
+ )
213
219
 
214
220
  yield CPTuple(
215
221
  config=tuple_.config,
@@ -75,6 +75,26 @@ LANGGRAPH_PY_MINOR = tuple(map(int, __version__.split(".")[:2]))
75
75
  USE_NEW_INTERRUPTS = LANGGRAPH_PY_MINOR >= (0, 6)
76
76
 
77
77
 
78
+ async def _decrypt_response(data: dict, model: str, fields: list[str]) -> dict:
79
+ from langgraph_api.encryption.middleware import decrypt_response # noqa: PLC0415
80
+
81
+ return await decrypt_response(data, model, fields) # type: ignore[arg-type]
82
+
83
+
84
+ async def _encrypt_request(data: dict, model: str, fields: list[str]) -> dict:
85
+ from langgraph_api.encryption.middleware import encrypt_request # noqa: PLC0415
86
+
87
+ return await encrypt_request(data, model, fields) # type: ignore[arg-type]
88
+
89
+
90
+ def _using_custom_encryption() -> bool:
91
+ from langgraph_api.encryption.shared import ( # noqa: PLC0415
92
+ using_custom_encryption,
93
+ )
94
+
95
+ return using_custom_encryption()
96
+
97
+
78
98
  def _run_stream_mode_matches(event_mode: str, stream_mode: list[str] | None) -> bool:
79
99
  """Return True if a published run-stream event matches the join filter."""
80
100
  if not stream_mode:
@@ -279,6 +299,18 @@ class Assistants(Authenticated):
279
299
 
280
300
  assistant_id = _ensure_uuid(assistant_id)
281
301
  metadata = metadata if metadata is not None else {}
302
+ custom_encryption = _using_custom_encryption()
303
+ if custom_encryption:
304
+ decrypted = await _decrypt_response(
305
+ {"config": config, "context": context, "metadata": metadata},
306
+ "assistant",
307
+ ["config", "context", "metadata"],
308
+ )
309
+ config, context, metadata = (
310
+ decrypted["config"],
311
+ decrypted["context"],
312
+ decrypted["metadata"],
313
+ )
282
314
  filters = await Assistants.handle_event(
283
315
  ctx,
284
316
  "create",
@@ -311,9 +343,14 @@ class Assistants(Authenticated):
311
343
  None,
312
344
  )
313
345
  if existing_assistant:
314
- if filters and not _check_filter_match(
315
- existing_assistant["metadata"], filters
316
- ):
346
+ existing_metadata = existing_assistant["metadata"]
347
+ if custom_encryption:
348
+ existing_metadata = (
349
+ await _decrypt_response(
350
+ {"metadata": existing_metadata}, "assistant", ["metadata"]
351
+ )
352
+ )["metadata"]
353
+ if filters and not _check_filter_match(existing_metadata, filters):
317
354
  raise HTTPException(
318
355
  status_code=409, detail=f"Assistant {assistant_id} already exists"
319
356
  )
@@ -352,6 +389,13 @@ class Assistants(Authenticated):
352
389
  "name": name,
353
390
  "description": description,
354
391
  }
392
+ if custom_encryption and not system:
393
+ new_assistant = await _encrypt_request(
394
+ new_assistant, "assistant", ["config", "context", "metadata"]
395
+ )
396
+ new_version = await _encrypt_request(
397
+ new_version, "assistant", ["config", "context", "metadata"]
398
+ )
355
399
  conn.store["assistants"].append(new_assistant)
356
400
  conn.store["assistant_versions"].append(new_version)
357
401
 
@@ -394,6 +438,18 @@ class Assistants(Authenticated):
394
438
  assistant_id = _ensure_uuid(assistant_id)
395
439
  metadata = metadata if metadata is not None else {}
396
440
  config = config if config is not None else {}
441
+ custom_encryption = _using_custom_encryption()
442
+ if custom_encryption:
443
+ decrypted = await _decrypt_response(
444
+ {"config": config, "context": context, "metadata": metadata},
445
+ "assistant",
446
+ ["config", "context", "metadata"],
447
+ )
448
+ config, context, metadata = (
449
+ decrypted["config"],
450
+ decrypted["context"],
451
+ decrypted["metadata"],
452
+ )
397
453
  filters = await Assistants.handle_event(
398
454
  ctx,
399
455
  "update",
@@ -430,7 +486,12 @@ class Assistants(Authenticated):
430
486
  raise HTTPException(
431
487
  status_code=404, detail=f"Assistant {assistant_id} not found"
432
488
  )
433
- elif filters and not _check_filter_match(assistant["metadata"], filters):
489
+ stored_assistant = assistant
490
+ if custom_encryption:
491
+ assistant = await _decrypt_response(
492
+ assistant, "assistant", ["config", "context", "metadata"]
493
+ )
494
+ if filters and not _check_filter_match(assistant["metadata"], filters):
434
495
  raise HTTPException(
435
496
  status_code=404, detail=f"Assistant {assistant_id} not found"
436
497
  )
@@ -464,10 +525,16 @@ class Assistants(Authenticated):
464
525
  description if description is not None else assistant.get("description")
465
526
  ),
466
527
  }
528
+ if custom_encryption:
529
+ new_version_entry = await _encrypt_request(
530
+ new_version_entry,
531
+ "assistant",
532
+ ["config", "context", "metadata"],
533
+ )
467
534
  conn.store["assistant_versions"].append(new_version_entry)
468
535
 
469
536
  # Update assistants table
470
- assistant.update(
537
+ stored_assistant.update(
471
538
  {
472
539
  "graph_id": new_version_entry["graph_id"],
473
540
  "config": new_version_entry["config"],
@@ -485,7 +552,7 @@ class Assistants(Authenticated):
485
552
  )
486
553
 
487
554
  async def _yield_updated():
488
- yield assistant
555
+ yield stored_assistant
489
556
 
490
557
  return _yield_updated()
491
558
 
@@ -2478,6 +2545,18 @@ class Runs(Authenticated):
2478
2545
  if not assistant:
2479
2546
  return _empty_generator()
2480
2547
 
2548
+ custom_encryption = _using_custom_encryption()
2549
+ if custom_encryption:
2550
+ assistant = await _decrypt_response(
2551
+ assistant, "assistant", ["metadata", "config", "context"]
2552
+ )
2553
+ kwargs = await _decrypt_response(
2554
+ kwargs, "run", ["input", "command", "config", "context"]
2555
+ )
2556
+ metadata = (
2557
+ await _decrypt_response({"metadata": metadata}, "run", ["metadata"])
2558
+ )["metadata"]
2559
+
2481
2560
  thread_id = _ensure_uuid(thread_id) if thread_id else None
2482
2561
  run_id = _ensure_uuid(run_id) if run_id else None
2483
2562
  metadata = metadata if metadata is not None else {}
@@ -2511,6 +2590,12 @@ class Runs(Authenticated):
2511
2590
  existing_thread = next(
2512
2591
  (t for t in conn.store["threads"] if t["thread_id"] == thread_id), None
2513
2592
  )
2593
+ if custom_encryption and existing_thread:
2594
+ existing_thread = await _decrypt_response(
2595
+ existing_thread,
2596
+ "thread",
2597
+ ["metadata", "config"],
2598
+ )
2514
2599
  # Automatically enforce assistant ownership for non-system assistants
2515
2600
  # by calling the user's assistant read auth handler.
2516
2601
  if assistant.get("metadata", {}).get("created_by") != "system":
@@ -2662,6 +2747,26 @@ class Runs(Authenticated):
2662
2747
  updated_at=datetime.now(UTC),
2663
2748
  langsmith_session_name=langsmith_session_name,
2664
2749
  )
2750
+ if custom_encryption:
2751
+ # Unlike Go core, in-memory workers read nested run kwargs and thread
2752
+ # config directly from the store, so those containers must stay readable.
2753
+ encrypted_thread = await _encrypt_request(
2754
+ thread if not existing_thread else existing_thread,
2755
+ "thread",
2756
+ ["metadata"],
2757
+ )
2758
+ stored_thread = next(
2759
+ t
2760
+ for t in conn.store["threads"]
2761
+ if t["thread_id"] == encrypted_thread["thread_id"]
2762
+ )
2763
+ stored_thread.update(encrypted_thread)
2764
+ new_run["kwargs"] = await _encrypt_request(
2765
+ new_run["kwargs"],
2766
+ "run",
2767
+ ["input", "config", "context", "command"],
2768
+ )
2769
+ new_run = Run(**await _encrypt_request(new_run, "run", ["metadata"]))
2665
2770
  conn.store["runs"].append(new_run)
2666
2771
 
2667
2772
  async def _yield_new():
@@ -36,7 +36,11 @@ dev = [
36
36
  [tool.hatch.version]
37
37
  path = "langgraph_runtime_inmem/__init__.py"
38
38
 
39
+ [tool.hatch.build.targets.wheel]
40
+ core-metadata-version = "2.4"
41
+
39
42
  [tool.hatch.build.targets.sdist]
43
+ core-metadata-version = "2.4"
40
44
  exclude = [
41
45
  "tests/",
42
46
  "docs/",