langgraph-api 0.0.41__py3-none-any.whl → 0.0.43__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.

Potentially problematic release.


This version of langgraph-api might be problematic. Click here for more details.

@@ -124,6 +124,7 @@ async def create_assistant(request: ApiRequest) -> ApiResponse:
124
124
  metadata=payload.get("metadata") or {},
125
125
  if_exists=payload.get("if_exists") or "raise",
126
126
  name=payload.get("name") or "Untitled",
127
+ description=payload.get("description"),
127
128
  )
128
129
  return ApiResponse(await fetchone(assistant, not_found_code=409))
129
130
 
@@ -312,6 +313,7 @@ async def patch_assistant(
312
313
  graph_id=payload.get("graph_id"),
313
314
  metadata=payload.get("metadata"),
314
315
  name=payload.get("name"),
316
+ description=payload.get("description"),
315
317
  )
316
318
  return ApiResponse(await fetchone(assistant))
317
319
 
langgraph_api/api/runs.py CHANGED
@@ -273,7 +273,7 @@ async def wait_run_stateless(request: ApiRequest):
273
273
  except TimeoutError:
274
274
  yield b"\n"
275
275
  except asyncio.CancelledError:
276
- stream.cancel()
276
+ stream.cancel("Run stream cancelled")
277
277
  await stream
278
278
  raise
279
279
 
langgraph_api/asyncio.py CHANGED
@@ -63,7 +63,9 @@ async def wait_if_not_done(coro: Coroutine[Any, Any, T], done: ValueEvent) -> T:
63
63
  async with asyncio.TaskGroup() as tg:
64
64
  coro_task = tg.create_task(coro)
65
65
  done_task = tg.create_task(done.wait())
66
- coro_task.add_done_callback(lambda _: done_task.cancel())
66
+ coro_task.add_done_callback(
67
+ lambda _: done_task.cancel("Coro task completed")
68
+ )
67
69
  done_task.add_done_callback(lambda _: coro_task.cancel(done._value))
68
70
  try:
69
71
  return await coro_task
@@ -131,6 +133,7 @@ class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
131
133
  cancel: bool = False,
132
134
  wait: bool = True,
133
135
  taskset: set[asyncio.Task] | None = None,
136
+ taskgroup_name: str | None = None,
134
137
  ) -> None:
135
138
  self.tasks = taskset if taskset is not None else set()
136
139
  self.cancel = cancel
@@ -140,6 +143,7 @@ class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
140
143
  task.add_done_callback(partial(self._create_task_done_callback, ()))
141
144
  for coro in coros:
142
145
  self.create_task(coro)
146
+ self.taskgroup_name = f" {taskgroup_name} " if taskgroup_name else ""
143
147
 
144
148
  def _create_task_done_callback(
145
149
  self, ignore_exceptions: tuple[Exception, ...], task: asyncio.Task
@@ -175,7 +179,7 @@ class SimpleTaskGroup(AbstractAsyncContextManager["SimpleTaskGroup"]):
175
179
  # cancel all tasks
176
180
  if self.cancel:
177
181
  for task in tasks:
178
- task.cancel()
182
+ task.cancel(f"Task group{self.taskgroup_name}cancelled.")
179
183
  # wait for all tasks
180
184
  if self.wait:
181
185
  await asyncio.gather(*tasks, return_exceptions=True)
langgraph_api/config.py CHANGED
@@ -105,6 +105,20 @@ class StoreConfig(TypedDict, total=False):
105
105
  ttl: TTLConfig
106
106
 
107
107
 
108
+ class CheckpointerConfig(TypedDict, total=False):
109
+ """Configuration for the built-in checkpointer, which handles checkpointing of state.
110
+
111
+ If omitted, no checkpointer is set up (the object store will still be present, however).
112
+ """
113
+
114
+ ttl: ThreadTTLConfig | None
115
+ """Optional. Defines the TTL (time-to-live) behavior configuration.
116
+
117
+ If provided, the checkpointer will apply TTL settings according to the configuration.
118
+ If omitted, no TTL behavior is configured.
119
+ """
120
+
121
+
108
122
  # env
109
123
 
110
124
  env = Config()
@@ -223,9 +237,14 @@ def _parse_thread_ttl(value: str | None) -> ThreadTTLConfig | None:
223
237
  }
224
238
 
225
239
 
240
+ CHECKPOINTER_CONFIG: CheckpointerConfig | None = env(
241
+ "LANGGRAPH_CHECKPOINTER", cast=_parse_json, default=None
242
+ )
226
243
  THREAD_TTL: ThreadTTLConfig | None = env(
227
244
  "LANGGRAPH_THREAD_TTL", cast=_parse_thread_ttl, default=None
228
245
  )
246
+ if THREAD_TTL is None and CHECKPOINTER_CONFIG is not None:
247
+ THREAD_TTL = CHECKPOINTER_CONFIG.get("ttl")
229
248
 
230
249
  N_JOBS_PER_WORKER = env("N_JOBS_PER_WORKER", cast=int, default=10)
231
250
  BG_JOB_TIMEOUT_SECS = env("BG_JOB_TIMEOUT_SECS", cast=float, default=3600)
langgraph_api/graph.py CHANGED
@@ -291,7 +291,7 @@ def _handle_exception(task: asyncio.Task) -> None:
291
291
  async def stop_remote_graphs() -> None:
292
292
  logger.info("Cancelling remote graphs")
293
293
  for task in js_bg_tasks:
294
- task.cancel()
294
+ task.cancel("Stopping remote graphs.")
295
295
 
296
296
 
297
297
  def verify_graphs() -> None:
langgraph_api/lifespan.py CHANGED
@@ -47,7 +47,9 @@ async def lifespan(
47
47
  await start_pool()
48
48
  await collect_graphs_from_env(True)
49
49
  try:
50
- async with SimpleTaskGroup(cancel=True, taskset=taskset) as tg:
50
+ async with SimpleTaskGroup(
51
+ cancel=True, taskset=taskset, taskgroup_name="Lifespan"
52
+ ) as tg:
51
53
  tg.create_task(metadata_loop())
52
54
  if config.N_JOBS_PER_WORKER > 0:
53
55
  tg.create_task(queue())
langgraph_api/schema.py CHANGED
@@ -73,6 +73,8 @@ class Assistant(TypedDict):
73
73
  """The ID of the graph."""
74
74
  name: str
75
75
  """The name of the assistant."""
76
+ description: str | None
77
+ """The description of the assistant."""
76
78
  config: Config
77
79
  """The assistant config."""
78
80
  created_at: datetime
@@ -25,7 +25,8 @@ async def thread_ttl_sweep_loop():
25
25
  )
26
26
  sweep_interval_minutes = thread_ttl_config.get("sweep_interval_minutes", 5)
27
27
  await logger.ainfo(
28
- "Starting thread TTL sweeper",
28
+ f"Starting thread TTL sweeper with interval {sweep_interval_minutes} minutes",
29
+ strategy=strategy,
29
30
  interval_minutes=sweep_interval_minutes,
30
31
  )
31
32
 
@@ -38,7 +39,7 @@ async def thread_ttl_sweep_loop():
38
39
  threads_processed, threads_deleted = await Threads.sweep_ttl(conn)
39
40
  if threads_processed > 0:
40
41
  await logger.ainfo(
41
- "Thread TTL sweep completed",
42
+ f"Thread TTL sweep completed. Processed {threads_processed}",
42
43
  threads_processed=threads_processed,
43
44
  threads_deleted=threads_deleted,
44
45
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: langgraph-api
3
- Version: 0.0.41
3
+ Version: 0.0.43
4
4
  Summary:
5
5
  License: Elastic-2.0
6
6
  Author: Nuno Campos
@@ -1,15 +1,15 @@
1
1
  LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
2
2
  langgraph_api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  langgraph_api/api/__init__.py,sha256=qNcg8QJydef0gM-vYJlxITMRZw-9r1vw8zqm2raqqYE,5493
4
- langgraph_api/api/assistants.py,sha256=0s6Bihek8g7hPkldFoikIe1zfbRd2SBzrBLfY4smL0o,14249
4
+ langgraph_api/api/assistants.py,sha256=WwaBtx1MpGn9gdJ8P9fkorJHMVrJKHt1nvtw0OCZcdw,14353
5
5
  langgraph_api/api/mcp.py,sha256=KbR19dtFCpJEiKYj3IfepAuJij8YZVELuVp7JY_yu_o,13754
6
6
  langgraph_api/api/meta.py,sha256=ifJ_Ki0Qf2DYbmY6OKlqKhLGxbt55gm0lEqH1A0cJbw,2790
7
7
  langgraph_api/api/openapi.py,sha256=f9gfmWN2AMKNUpLCpSgZuw_aeOF9jCXPdOtFT5PaTWM,10960
8
- langgraph_api/api/runs.py,sha256=uijgtrw_FSf1lZHsx8pM-CIj_ur2O88Y7ys-CJZ4SNQ,17988
8
+ langgraph_api/api/runs.py,sha256=qAXfgZjjaMBfwPnlnAogvemtKZZeshNSIMQqcW20tGs,18010
9
9
  langgraph_api/api/store.py,sha256=XNNZFRlvgReidq9u7mg-i7pjwz21BdP9Qw3Jr5Ra9Fk,5447
10
10
  langgraph_api/api/threads.py,sha256=QbAy7MRupWKDUhmC6_LKU2ExJbLJ6Z-CJG2gSpcMXtc,9163
11
11
  langgraph_api/api/ui.py,sha256=LiOZVewKOPbKEykCm30hCEaOA7vuS_Ti5hB32EEy4vw,2082
12
- langgraph_api/asyncio.py,sha256=hVuAxWTHoUyNqTzcIEKTkAvh7HFrdGK6WIDowDxORAE,8397
12
+ langgraph_api/asyncio.py,sha256=h0eZ7aoDGnJpoxnHLZABVlj1jQ78UxjgiHntTmAEWek,8613
13
13
  langgraph_api/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
14
  langgraph_api/auth/custom.py,sha256=sPYkF-xTdGIn_WMSE2tfR_azg_azzXPRWwUfCye6GQ8,21401
15
15
  langgraph_api/auth/langsmith/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -20,10 +20,10 @@ langgraph_api/auth/noop.py,sha256=Bk6Nf3p8D_iMVy_OyfPlyiJp_aEwzL-sHrbxoXpCbac,58
20
20
  langgraph_api/auth/studio_user.py,sha256=FzFQRROKDlA9JjtBuwyZvk6Mbwno5M9RVYjDO6FU3F8,186
21
21
  langgraph_api/cli.py,sha256=X76TYnErQPjRkZhK-n6gthCUmtItkwqVrRPvwZswF5Y,12335
22
22
  langgraph_api/command.py,sha256=3O9v3i0OPa96ARyJ_oJbLXkfO8rPgDhLCswgO9koTFA,768
23
- langgraph_api/config.py,sha256=y-dmkmd929WDosIO0nUmcQicZN2XozJrVHzeBGtLhUg,9418
23
+ langgraph_api/config.py,sha256=N6P_6jtr6rNGC7T5lSWoCbaTQhXbWi-F4YmH0NyPbAI,10141
24
24
  langgraph_api/cron_scheduler.py,sha256=9yzbbGxzNgJdIg4ZT7yu2oTwT_wRuPxD1c2sbbd52xs,2630
25
25
  langgraph_api/errors.py,sha256=Bu_i5drgNTyJcLiyrwVE_6-XrSU50BHf9TDpttki9wQ,1690
26
- langgraph_api/graph.py,sha256=jpMMMgn8uHqDc3gPu02tF7B8af0TV153t2F_kodcBvI,18799
26
+ langgraph_api/graph.py,sha256=7NKAjtslNZq65J1tYhvJfpcP_P8JVTRra7vOfGuUWvc,18824
27
27
  langgraph_api/http.py,sha256=gYbxxjY8aLnsXeJymcJ7G7Nj_yToOGpPYQqmZ1_ggfA,5240
28
28
  langgraph_api/js/.gitignore,sha256=l5yI6G_V6F1600I1IjiUKn87f4uYIrBAYU1MOyBBhg4,59
29
29
  langgraph_api/js/base.py,sha256=xkBp5bwRrbpMFaAMViEU-qIlnsJuu3X_G8sa1pqNZK0,227
@@ -61,7 +61,7 @@ langgraph_api/js/tests/graphs/yarn.lock,sha256=i2AAIgXA3XBLM8-oU45wgUefCSG-Tne4g
61
61
  langgraph_api/js/tests/parser.test.mts,sha256=dEC8KTqKygeb1u39ZvpPqCT4HtfPD947nLmITt2buxA,27883
62
62
  langgraph_api/js/tests/utils.mts,sha256=q1V9gvT63v95onlfK9W4iv3n9ZJO3h-0RD9TdDYuRyY,439
63
63
  langgraph_api/js/yarn.lock,sha256=JtMDtRVX9kr9lK0rbWRQsGHYbkQSmzNa4NwTunBv58U,82773
64
- langgraph_api/lifespan.py,sha256=0F4Xn6IC4wUfvBWbm6KCJ-jiF0S7iWDOHJYJZ-A3o3s,2739
64
+ langgraph_api/lifespan.py,sha256=rrmxgNrAxWa_D1H8AZTBKXoQoyiLeDv_C5z-byuxTWM,2788
65
65
  langgraph_api/logging.py,sha256=JJIzbNIgLCN6ClQ3tA-Mm5ffuBGvpRDSZsEvnIlsuu4,3693
66
66
  langgraph_api/metadata.py,sha256=5Mu3MUtUc-iIocU3X2SZDoGIqnUmTdT3517MhP94npI,3495
67
67
  langgraph_api/middleware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -72,13 +72,13 @@ langgraph_api/models/run.py,sha256=85-pyyvosFAot8WrWl2QjCje-2c4lhattYvoAEMqztA,1
72
72
  langgraph_api/patch.py,sha256=82xjuFqY7tgrUm-k1XWHI6k8S6QovSD0zhe--8_xW4o,1296
73
73
  langgraph_api/queue_entrypoint.py,sha256=4xICUxXarNV8DhnaqAMhVi3xCmyVKCL3J5NzHxPA9Xc,1835
74
74
  langgraph_api/route.py,sha256=fM4qYCGbmH0a3_cV8uKocb1sLklehxO6HhdRXqLK6OM,4421
75
- langgraph_api/schema.py,sha256=hNbg6ep2wiGBBtBJVNBgMYA8uC33AfaqhRXXVUY_At8,5361
75
+ langgraph_api/schema.py,sha256=Frh_YOC3S1cDAMPUVanNi78ooSXK2WFpu9YkIVz5h14,5433
76
76
  langgraph_api/serde.py,sha256=D5t_HeABMYKRAsoXWaWG0IsdaYM8yOXIez2wJUTIgT8,3963
77
77
  langgraph_api/server.py,sha256=bnXOOYztQmqR-QVpEFoRWB5Fzd33PuEIlwBK2R7W8NE,4849
78
78
  langgraph_api/sse.py,sha256=2wNodCOP2eg7a9mpSu0S3FQ0CHk2BBV_vv0UtIgJIcc,4034
79
79
  langgraph_api/state.py,sha256=8jx4IoTCOjTJuwzuXJKKFwo1VseHjNnw_CCq4x1SW14,2284
80
80
  langgraph_api/stream.py,sha256=lhjnom-T8GbquUZry-KSkajnqYjElaIERhPiXPtpw1E,11354
81
- langgraph_api/thread_ttl.py,sha256=ubkWMymTR7p9nGWd60-WKEOQ20ZgIkWB6WGstQUmRS4,1658
81
+ langgraph_api/thread_ttl.py,sha256=QS9C7cx63KBh82_I6DqOb42-5mLTqTvneL8FJeX3aqU,1765
82
82
  langgraph_api/utils.py,sha256=92mSti9GfGdMRRWyESKQW5yV-75Z9icGHnIrBYvdypU,3619
83
83
  langgraph_api/validation.py,sha256=Qo3EkSRXzIRe3GRuqRWbElTcUXRMXMyA1w0VbMvdwME,4934
84
84
  langgraph_api/webhook.py,sha256=1ncwO0rIZcj-Df9sxSnFEzd1gP1bfS4okeZQS8NSRoE,1382
@@ -88,17 +88,16 @@ langgraph_license/middleware.py,sha256=_ODIYzQkymr6W9_Fp9wtf1kAQspnpsmr53xuzyF2G
88
88
  langgraph_license/validation.py,sha256=Uu_G8UGO_WTlLsBEY0gTVWjRR4czYGfw5YAD3HLZoj0,203
89
89
  langgraph_storage/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
90
90
  langgraph_storage/checkpoint.py,sha256=Qq0y6vdh27qdF3h5nOLT5CcX9Rj2bcFqkVOMeCaGoK4,4036
91
- langgraph_storage/database.py,sha256=ys2glLtQaspfIY-_EgqEOT3b7a5WqBwuE_9I5VAh6FM,5821
91
+ langgraph_storage/database.py,sha256=sZjZvMcvbr_6dX0d1YrYEccVuQozIfkiWt8bdlXGVYU,5849
92
92
  langgraph_storage/inmem_stream.py,sha256=LjJSAxsh_E0ywqEMzdWJk8Hy_Jn9oQByzycss-fANng,3264
93
- langgraph_storage/ops.py,sha256=6NxeirfuDAKghHc328zejnbTsijg6U80Rlt5rXTC2dc,75392
94
- langgraph_storage/queue.py,sha256=wyBcY1V_8zah0Z2e5hpovs_dsIPPPfUJQCly2rxFWvo,7039
93
+ langgraph_storage/ops.py,sha256=S2qXgfyuCr5qkKLqIV-kZNRK924bBSLZ2qdLtExfi-M,75939
94
+ langgraph_storage/queue.py,sha256=d2hykynk3rqrixEdrD_zl0Zyo63wJs3S4SJOzFeF-SA,7153
95
95
  langgraph_storage/retry.py,sha256=XmldOP4e_H5s264CagJRVnQMDFcEJR_dldVR1Hm5XvM,763
96
96
  langgraph_storage/store.py,sha256=JB9jZ87GE19MVN9wgl3-esgR2eIkeipws9q6qsPWkgc,3399
97
- langgraph_storage/ttl_dict.py,sha256=FlpEY8EANeXWKo_G5nmIotPquABZGyIJyk6HD9u6vqY,1533
98
97
  logging.json,sha256=3RNjSADZmDq38eHePMm1CbP6qZ71AmpBtLwCmKU9Zgo,379
99
98
  openapi.json,sha256=-25y3NRQ88e_944UXo76Goa34HJhC7pj6I9tjYUwvuE,131492
100
- langgraph_api-0.0.41.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
101
- langgraph_api-0.0.41.dist-info/METADATA,sha256=0yewVWyEzw_HfLoOEBCQS-OymzT8q0JUg_6ux-SQE8Y,4167
102
- langgraph_api-0.0.41.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
103
- langgraph_api-0.0.41.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
104
- langgraph_api-0.0.41.dist-info/RECORD,,
99
+ langgraph_api-0.0.43.dist-info/LICENSE,sha256=ZPwVR73Biwm3sK6vR54djCrhaRiM4cAD2zvOQZV8Xis,3859
100
+ langgraph_api-0.0.43.dist-info/METADATA,sha256=GVLyrH4YCy2zRADllPQpBtuD8BHQwVaBitZPZlefgeQ,4167
101
+ langgraph_api-0.0.43.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
102
+ langgraph_api-0.0.43.dist-info/entry_points.txt,sha256=3EYLgj89DfzqJHHYGxPH4A_fEtClvlRbWRUHaXO7hj4,77
103
+ langgraph_api-0.0.43.dist-info/RECORD,,
@@ -23,6 +23,7 @@ class Assistant(TypedDict):
23
23
  assistant_id: UUID
24
24
  graph_id: str
25
25
  name: str
26
+ description: str | None
26
27
  created_at: NotRequired[datetime]
27
28
  updated_at: NotRequired[datetime]
28
29
  config: dict[str, Any]
langgraph_storage/ops.py CHANGED
@@ -177,6 +177,7 @@ class Assistants(Authenticated):
177
177
  if_exists: OnConflictBehavior,
178
178
  name: str,
179
179
  ctx: Auth.types.BaseAuthContext | None = None,
180
+ description: str | None = None,
180
181
  ) -> AsyncIterator[Assistant]:
181
182
  """Insert an assistant."""
182
183
  assistant_id = _ensure_uuid(assistant_id)
@@ -224,6 +225,7 @@ class Assistants(Authenticated):
224
225
  "created_at": now,
225
226
  "updated_at": now,
226
227
  "version": 1,
228
+ "description": description,
227
229
  }
228
230
  new_version = {
229
231
  "assistant_id": assistant_id,
@@ -251,16 +253,20 @@ class Assistants(Authenticated):
251
253
  graph_id: str | None = None,
252
254
  metadata: MetadataInput | None = None,
253
255
  name: str | None = None,
256
+ description: str | None = None,
254
257
  ctx: Auth.types.BaseAuthContext | None = None,
255
258
  ) -> AsyncIterator[Assistant]:
256
259
  """Update an assistant.
257
260
 
258
261
  Args:
262
+ conn: The connection to the in-memory store.
259
263
  assistant_id: The assistant ID.
260
264
  graph_id: The graph ID.
261
265
  config: The assistant config.
262
266
  metadata: The assistant metadata.
263
267
  name: The assistant name.
268
+ description: The assistant description.
269
+ ctx: The auth context.
264
270
 
265
271
  Returns:
266
272
  return the updated assistant model.
@@ -317,6 +323,9 @@ class Assistants(Authenticated):
317
323
  "metadata": metadata if metadata is not None else assistant["metadata"],
318
324
  "created_at": now,
319
325
  "name": name if name is not None else assistant["name"],
326
+ "description": description
327
+ if description is not None
328
+ else assistant["description"],
320
329
  }
321
330
  conn.store["assistant_versions"].append(new_version_entry)
322
331
 
@@ -327,6 +336,9 @@ class Assistants(Authenticated):
327
336
  "config": new_version_entry["config"],
328
337
  "metadata": new_version_entry["metadata"],
329
338
  "name": name if name is not None else assistant["name"],
339
+ "description": description
340
+ if description is not None
341
+ else assistant["description"],
330
342
  "updated_at": now,
331
343
  "version": new_version,
332
344
  }
@@ -1350,7 +1362,7 @@ class Runs(Authenticated):
1350
1362
  # Get queue for this run
1351
1363
  queue = await Runs.Stream.subscribe(run_id)
1352
1364
 
1353
- async with SimpleTaskGroup(cancel=True) as tg:
1365
+ async with SimpleTaskGroup(cancel=True, taskgroup_name="Runs.enter") as tg:
1354
1366
  done = ValueEvent()
1355
1367
  tg.create_task(listen_for_cancellation(queue, run_id, done))
1356
1368
 
@@ -141,9 +141,9 @@ async def queue():
141
141
  bb.deactivate()
142
142
  logger.info("Shutting down background workers")
143
143
  for task in WORKERS:
144
- task.cancel()
144
+ task.cancel("Shutting down background workers.")
145
145
  for task in WEBHOOKS:
146
- task.cancel()
146
+ task.cancel("Shutting down webhooks for background workers.")
147
147
  await asyncio.wait_for(
148
148
  asyncio.gather(*WORKERS, *WEBHOOKS, return_exceptions=True),
149
149
  SHUTDOWN_GRACE_PERIOD_SECS,
@@ -156,20 +156,21 @@ def _enable_blockbuster() -> BlockBuster:
156
156
  bb.activate()
157
157
  # Note, we've cached this call in langsmith==0.3.21 so it shouldn't raise anyway
158
158
  # but we don't want to raise teh minbound just for that.
159
- for func in ["os.stat", "io.TextIOWrapper.read"]:
159
+ bb.functions["os.stat"].deactivate()
160
+ for func in ("io.TextIOWrapper.read",):
160
161
  bb.functions[func].can_block_in("langsmith/client.py", "_default_retry_config")
161
162
  # Only triggers in python 3.11 for getting subgraphs
162
163
  # Will be unnecessary once we cache the assistant schemas
163
164
  bb.functions[func].can_block_in(
164
165
  "langgraph/pregel/utils.py", "get_function_nonlocals"
165
166
  )
166
- for module, func in [
167
+ for module, func in (
167
168
  ("uvicorn/lifespan/on.py", "startup"),
168
169
  ("uvicorn/lifespan/on.py", "shutdown"),
169
170
  ("ansitowin32.py", "write_plain_text"),
170
171
  ("logging/__init__.py", "flush"),
171
172
  ("logging/__init__.py", "emit"),
172
- ]:
173
+ ):
173
174
  bb.functions["io.TextIOWrapper.write"].can_block_in(module, func)
174
175
  bb.functions["io.BufferedWriter.write"].can_block_in(module, func)
175
176
  return bb
@@ -1,54 +0,0 @@
1
- import asyncio
2
-
3
-
4
- # TODO: Make this class thread-safe
5
- class TTLDict:
6
- def __init__(self, timeout=10):
7
- self._store = {}
8
- self._timeouts = {}
9
- self._tasks = {} # Track tasks for key expiry
10
- self._timeout = timeout
11
-
12
- async def _remove_key_after_timeout(self, key):
13
- await asyncio.sleep(self._timeouts[key])
14
- if key in self._store:
15
- del self._store[key]
16
- del self._timeouts[key]
17
-
18
- def set(self, key, value, timeout=None):
19
- self._store[key] = value
20
- key_timeout = timeout if timeout is not None else self._timeout
21
- self._timeouts[key] = key_timeout
22
-
23
- # Cancel the previous task if it exists
24
- if key in self._tasks:
25
- self._tasks[key].cancel()
26
-
27
- # Schedule the removal of the key
28
- self._tasks[key] = asyncio.create_task(self._remove_key_after_timeout(key))
29
-
30
- def get(self, key, default=None):
31
- return self._store.get(key, default)
32
-
33
- def __contains__(self, key):
34
- return key in self._store
35
-
36
- def remove(self, key):
37
- if key in self._store:
38
- del self._store[key]
39
- del self._timeouts[key]
40
- if key in self._tasks:
41
- self._tasks[key].cancel()
42
- del self._tasks[key]
43
-
44
- def keys(self):
45
- return self._store.keys()
46
-
47
- def values(self):
48
- return self._store.values()
49
-
50
- def items(self):
51
- return self._store.items()
52
-
53
- def __repr__(self):
54
- return f"{self.__class__.__name__}({self._store})"