langgraph-api 0.2.72__tar.gz → 0.2.75__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.

Potentially problematic release.


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

Files changed (107) hide show
  1. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/PKG-INFO +1 -1
  2. langgraph_api-0.2.75/langgraph_api/__init__.py +1 -0
  3. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/assistants.py +2 -0
  4. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/asyncio.py +5 -2
  5. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/cron_scheduler.py +1 -1
  6. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/graph.py +4 -3
  7. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/logging.py +1 -6
  8. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/state.py +7 -1
  9. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/store.py +1 -1
  10. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/stream.py +3 -3
  11. langgraph_api-0.2.75/langgraph_api/utils/config.py +140 -0
  12. langgraph_api-0.2.75/langgraph_api/utils/future.py +220 -0
  13. langgraph_api-0.2.75/langgraph_license/__init__.py +0 -0
  14. langgraph_api-0.2.72/langgraph_api/__init__.py +0 -1
  15. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/.gitignore +0 -0
  16. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/LICENSE +0 -0
  17. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/Makefile +0 -0
  18. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/README.md +0 -0
  19. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/benchmark/.gitignore +0 -0
  20. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/benchmark/Makefile +0 -0
  21. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/benchmark/README.md +0 -0
  22. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/benchmark/burst.js +0 -0
  23. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/benchmark/weather.js +0 -0
  24. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/constraints.txt +0 -0
  25. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/forbidden.txt +0 -0
  26. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/healthcheck.py +0 -0
  27. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/__init__.py +0 -0
  28. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/mcp.py +0 -0
  29. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/meta.py +0 -0
  30. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/openapi.py +0 -0
  31. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/runs.py +0 -0
  32. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/store.py +0 -0
  33. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/threads.py +0 -0
  34. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/api/ui.py +0 -0
  35. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/asgi_transport.py +0 -0
  36. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/__init__.py +0 -0
  37. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/custom.py +0 -0
  38. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/langsmith/__init__.py +0 -0
  39. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/langsmith/backend.py +0 -0
  40. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/langsmith/client.py +0 -0
  41. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/middleware.py +0 -0
  42. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/noop.py +0 -0
  43. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/auth/studio_user.py +0 -0
  44. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/cli.py +0 -0
  45. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/command.py +0 -0
  46. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/config.py +0 -0
  47. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/errors.py +0 -0
  48. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/http.py +0 -0
  49. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/.gitignore +0 -0
  50. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/.prettierrc +0 -0
  51. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/__init__.py +0 -0
  52. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/base.py +0 -0
  53. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/build.mts +0 -0
  54. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/client.http.mts +0 -0
  55. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/client.mts +0 -0
  56. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/errors.py +0 -0
  57. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/global.d.ts +0 -0
  58. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/package.json +0 -0
  59. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/remote.py +0 -0
  60. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/schema.py +0 -0
  61. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/graph.mts +0 -0
  62. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/load.hooks.mjs +0 -0
  63. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/preload.mjs +0 -0
  64. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/utils/files.mts +0 -0
  65. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/utils/importMap.mts +0 -0
  66. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/utils/pythonSchemas.mts +0 -0
  67. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/src/utils/serde.mts +0 -0
  68. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/sse.py +0 -0
  69. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/tsconfig.json +0 -0
  70. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/ui.py +0 -0
  71. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/js/yarn.lock +0 -0
  72. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/metadata.py +0 -0
  73. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/middleware/__init__.py +0 -0
  74. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/middleware/http_logger.py +0 -0
  75. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/middleware/private_network.py +0 -0
  76. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/middleware/request_id.py +0 -0
  77. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/models/__init__.py +0 -0
  78. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/models/run.py +0 -0
  79. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/patch.py +0 -0
  80. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/queue_entrypoint.py +0 -0
  81. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/route.py +0 -0
  82. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/schema.py +0 -0
  83. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/serde.py +0 -0
  84. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/server.py +0 -0
  85. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/sse.py +0 -0
  86. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/thread_ttl.py +0 -0
  87. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/tunneling/cloudflare.py +0 -0
  88. /langgraph_api-0.2.72/langgraph_api/utils.py → /langgraph_api-0.2.75/langgraph_api/utils/__init__.py +0 -0
  89. /langgraph_api-0.2.72/langgraph_license/__init__.py → /langgraph_api-0.2.75/langgraph_api/utils.py +0 -0
  90. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/validation.py +0 -0
  91. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/webhook.py +0 -0
  92. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_api/worker.py +0 -0
  93. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_license/validation.py +0 -0
  94. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/__init__.py +0 -0
  95. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/checkpoint.py +0 -0
  96. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/database.py +0 -0
  97. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/lifespan.py +0 -0
  98. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/metrics.py +0 -0
  99. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/ops.py +0 -0
  100. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/queue.py +0 -0
  101. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/retry.py +0 -0
  102. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/langgraph_runtime/store.py +0 -0
  103. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/logging.json +0 -0
  104. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/openapi.json +0 -0
  105. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/pyproject.toml +0 -0
  106. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/scripts/create_license.py +0 -0
  107. {langgraph_api-0.2.72 → langgraph_api-0.2.75}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: langgraph-api
3
- Version: 0.2.72
3
+ Version: 0.2.75
4
4
  Author-email: Nuno Campos <nuno@langchain.dev>, Will Fu-Hinthorn <will@langchain.dev>
5
5
  License: Elastic-2.0
6
6
  License-File: LICENSE
@@ -0,0 +1 @@
1
+ __version__ = "0.2.75"
@@ -2,6 +2,8 @@ from typing import Any
2
2
  from uuid import uuid4
3
3
 
4
4
  import structlog
5
+
6
+ # TODO: Remove dependency on langchain-core here.
5
7
  from langchain_core.runnables.utils import create_model
6
8
  from langgraph.pregel import Pregel
7
9
  from pydantic import TypeAdapter
@@ -6,7 +6,6 @@ from functools import partial
6
6
  from typing import Any, Generic, TypeVar
7
7
 
8
8
  import structlog
9
- from langgraph.utils.future import chain_future
10
9
 
11
10
  T = TypeVar("T")
12
11
 
@@ -130,11 +129,15 @@ def run_coroutine_threadsafe(
130
129
 
131
130
  def call_soon_in_main_loop(coro: Coroutine[Any, Any, T]) -> asyncio.Future[T]:
132
131
  """Run a coroutine in the main event loop."""
132
+ from langgraph_api.utils import future as lg_future
133
+
133
134
  if _MAIN_LOOP is None:
134
135
  raise RuntimeError("No event loop set")
135
136
  main_loop_fut = asyncio.ensure_future(coro, loop=_MAIN_LOOP)
136
137
  this_loop_fut = asyncio.get_running_loop().create_future()
137
- _MAIN_LOOP.call_soon_threadsafe(chain_future, main_loop_fut, this_loop_fut)
138
+ _MAIN_LOOP.call_soon_threadsafe(
139
+ lg_future.chain_future, main_loop_fut, this_loop_fut
140
+ )
138
141
  return this_loop_fut
139
142
 
140
143
 
@@ -2,10 +2,10 @@ import asyncio
2
2
  from random import random
3
3
 
4
4
  import structlog
5
- from langchain_core.runnables.config import run_in_executor
6
5
 
7
6
  from langgraph_api.models.run import create_valid_run
8
7
  from langgraph_api.utils import next_cron_date
8
+ from langgraph_api.utils.config import run_in_executor
9
9
  from langgraph_api.worker import set_auth_ctx_for_run
10
10
  from langgraph_runtime.database import connect
11
11
  from langgraph_runtime.ops import Crons
@@ -14,19 +14,18 @@ from uuid import UUID, uuid5
14
14
 
15
15
  import orjson
16
16
  import structlog
17
- from langchain_core.runnables.config import run_in_executor, var_child_runnable_config
18
17
  from langgraph.checkpoint.base import BaseCheckpointSaver
19
18
  from langgraph.constants import CONFIG_KEY_CHECKPOINTER, CONFIG_KEY_STORE
20
19
  from langgraph.graph import StateGraph
21
20
  from langgraph.pregel import Pregel
22
21
  from langgraph.store.base import BaseStore
23
- from langgraph.utils.config import ensure_config
24
22
  from starlette.exceptions import HTTPException
25
23
 
26
24
  from langgraph_api import asyncio as lg_asyncio
27
25
  from langgraph_api import config
28
26
  from langgraph_api.js.base import BaseRemotePregel, is_js_path
29
27
  from langgraph_api.schema import Config
28
+ from langgraph_api.utils.config import run_in_executor, var_child_runnable_config
30
29
 
31
30
  if TYPE_CHECKING:
32
31
  from langchain_core.embeddings import Embeddings
@@ -123,10 +122,12 @@ async def get_graph(
123
122
  store: BaseStore | None = None,
124
123
  ) -> AsyncIterator[Pregel]:
125
124
  """Return the runnable."""
125
+ from langgraph_api.utils import config as lg_config
126
+
126
127
  assert_graph_exists(graph_id)
127
128
  value = GRAPHS[graph_id]
128
129
  if graph_id in FACTORY_ACCEPTS_CONFIG:
129
- config = ensure_config(config)
130
+ config = lg_config.ensure_config(config)
130
131
  if store is not None and not config["configurable"].get(CONFIG_KEY_STORE):
131
132
  config["configurable"][CONFIG_KEY_STORE] = store
132
133
  if checkpointer is not None and not config["configurable"].get(
@@ -15,7 +15,6 @@ log_env = Config()
15
15
  LOG_JSON = log_env("LOG_JSON", cast=bool, default=False)
16
16
  LOG_COLOR = log_env("LOG_COLOR", cast=bool, default=True)
17
17
  LOG_LEVEL = log_env("LOG_LEVEL", cast=str, default="INFO")
18
- LOG_DICT_TRACEBACKS = log_env("LOG_DICT_TRACEBACKS", cast=bool, default=True)
19
18
 
20
19
  logging.getLogger().setLevel(LOG_LEVEL.upper())
21
20
  logging.getLogger("psycopg").setLevel(logging.WARNING)
@@ -104,11 +103,7 @@ shared_processors = [
104
103
  AddApiVersion(),
105
104
  structlog.processors.TimeStamper(fmt="iso", utc=True),
106
105
  structlog.processors.StackInfoRenderer(),
107
- (
108
- structlog.processors.dict_tracebacks
109
- if LOG_JSON and LOG_DICT_TRACEBACKS
110
- else structlog.processors.format_exc_info
111
- ),
106
+ structlog.processors.format_exc_info,
112
107
  structlog.processors.UnicodeDecoder(),
113
108
  AddLoggingContext(),
114
109
  ]
@@ -1,8 +1,14 @@
1
- from langchain_core.runnables.config import RunnableConfig
1
+ from __future__ import annotations
2
+
3
+ import typing
4
+
2
5
  from langgraph.types import StateSnapshot
3
6
 
4
7
  from langgraph_api.schema import Checkpoint, ThreadState
5
8
 
9
+ if typing.TYPE_CHECKING:
10
+ from langchain_core.runnables.config import RunnableConfig
11
+
6
12
 
7
13
  def runnable_config_to_checkpoint(
8
14
  config: RunnableConfig | None,
@@ -8,12 +8,12 @@ from random import choice
8
8
  from typing import Any
9
9
 
10
10
  import structlog
11
- from langchain_core.runnables.config import run_in_executor
12
11
  from langgraph.graph import StateGraph
13
12
  from langgraph.pregel import Pregel
14
13
  from langgraph.store.base import BaseStore
15
14
 
16
15
  from langgraph_api import config
16
+ from langgraph_api.utils.config import run_in_executor
17
17
 
18
18
  logger = structlog.stdlib.get_logger(__name__)
19
19
 
@@ -7,12 +7,12 @@ import langgraph.version
7
7
  import langsmith
8
8
  import structlog
9
9
  from langchain_core.messages import (
10
+ # TODO: Remove explicit dependency
10
11
  BaseMessage,
11
12
  BaseMessageChunk,
12
13
  convert_to_messages,
13
14
  message_chunk_to_message,
14
15
  )
15
- from langchain_core.runnables.config import run_in_executor
16
16
  from langgraph.errors import (
17
17
  EmptyChannelError,
18
18
  EmptyInputError,
@@ -33,6 +33,7 @@ from langgraph_api.js.base import BaseRemotePregel
33
33
  from langgraph_api.metadata import HOST, PLAN, USER_API_URL, incr_nodes
34
34
  from langgraph_api.schema import Run, StreamMode
35
35
  from langgraph_api.serde import json_dumpb
36
+ from langgraph_api.utils.config import run_in_executor
36
37
  from langgraph_runtime.checkpoint import Checkpointer
37
38
  from langgraph_runtime.ops import Runs
38
39
 
@@ -320,7 +321,6 @@ async def consume(stream: AnyStream, run_id: str, resumable: bool = False) -> No
320
321
  resumable=resumable,
321
322
  )
322
323
  except Exception as e:
323
- g = e
324
324
  if isinstance(e, ExceptionGroup):
325
325
  e = e.exceptions[0]
326
326
  await Runs.Stream.publish(
@@ -329,7 +329,7 @@ async def consume(stream: AnyStream, run_id: str, resumable: bool = False) -> No
329
329
  await run_in_executor(None, json_dumpb, e),
330
330
  resumable=resumable,
331
331
  )
332
- raise e from g
332
+ raise e
333
333
 
334
334
 
335
335
  def get_feedback_urls(run_id: str, feedback_keys: list[str]) -> dict[str, str]:
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import functools
5
+ import typing
6
+ from collections import ChainMap
7
+ from concurrent.futures import Executor
8
+ from contextvars import copy_context
9
+ from os import getenv
10
+ from typing import Any, ParamSpec, TypeVar, cast
11
+
12
+ from langgraph.constants import CONF
13
+
14
+ if typing.TYPE_CHECKING:
15
+ from langchain_core.runnables import RunnableConfig
16
+
17
+ try:
18
+ from langchain_core.runnables.config import (
19
+ var_child_runnable_config,
20
+ )
21
+ except ImportError:
22
+ var_child_runnable_config = None
23
+
24
+ CONFIG_KEYS = [
25
+ "tags",
26
+ "metadata",
27
+ "callbacks",
28
+ "run_name",
29
+ "max_concurrency",
30
+ "recursion_limit",
31
+ "configurable",
32
+ "run_id",
33
+ ]
34
+
35
+ COPIABLE_KEYS = [
36
+ "tags",
37
+ "metadata",
38
+ "callbacks",
39
+ "configurable",
40
+ ]
41
+
42
+ DEFAULT_RECURSION_LIMIT = int(getenv("LANGGRAPH_DEFAULT_RECURSION_LIMIT", "25"))
43
+
44
+ T = TypeVar("T")
45
+ P = ParamSpec("P")
46
+
47
+
48
+ def _is_not_empty(value: Any) -> bool:
49
+ if isinstance(value, list | tuple | dict):
50
+ return len(value) > 0
51
+ else:
52
+ return value is not None
53
+
54
+
55
+ def ensure_config(*configs: RunnableConfig | None) -> RunnableConfig:
56
+ """Return a config with all keys, merging any provided configs.
57
+
58
+ Args:
59
+ *configs: Configs to merge before ensuring defaults.
60
+
61
+ Returns:
62
+ RunnableConfig: The merged and ensured config.
63
+ """
64
+ empty = dict(
65
+ tags=[],
66
+ metadata=ChainMap(),
67
+ callbacks=None,
68
+ recursion_limit=DEFAULT_RECURSION_LIMIT,
69
+ configurable={},
70
+ )
71
+ if var_child_runnable_config is not None and (
72
+ var_config := var_child_runnable_config.get()
73
+ ):
74
+ empty.update(
75
+ {
76
+ k: v.copy() if k in COPIABLE_KEYS else v # type: ignore[attr-defined]
77
+ for k, v in var_config.items()
78
+ if _is_not_empty(v)
79
+ },
80
+ )
81
+ for config in configs:
82
+ if config is None:
83
+ continue
84
+ for k, v in config.items():
85
+ if _is_not_empty(v) and k in CONFIG_KEYS:
86
+ if k == CONF:
87
+ empty[k] = cast(dict, v).copy()
88
+ else:
89
+ empty[k] = v # type: ignore[literal-required]
90
+ for k, v in config.items():
91
+ if _is_not_empty(v) and k not in CONFIG_KEYS:
92
+ empty[CONF][k] = v
93
+ for key, value in empty[CONF].items():
94
+ if (
95
+ not key.startswith("__")
96
+ and isinstance(value, str | int | float | bool)
97
+ and key not in empty["metadata"]
98
+ ):
99
+ empty["metadata"][key] = value
100
+ return empty
101
+
102
+
103
+ async def run_in_executor(
104
+ executor_or_config: Executor | RunnableConfig | None,
105
+ func: typing.Callable[P, T],
106
+ *args: P.args,
107
+ **kwargs: P.kwargs,
108
+ ) -> T:
109
+ """Run a function in an executor.
110
+
111
+ Args:
112
+ executor_or_config: The executor or config to run in.
113
+ func (Callable[P, Output]): The function.
114
+ *args (Any): The positional arguments to the function.
115
+ **kwargs (Any): The keyword arguments to the function.
116
+
117
+ Returns:
118
+ Output: The output of the function.
119
+
120
+ Raises:
121
+ RuntimeError: If the function raises a StopIteration.
122
+ """
123
+
124
+ def wrapper() -> T:
125
+ try:
126
+ return func(*args, **kwargs)
127
+ except StopIteration as exc:
128
+ # StopIteration can't be set on an asyncio.Future
129
+ # it raises a TypeError and leaves the Future pending forever
130
+ # so we need to convert it to a RuntimeError
131
+ raise RuntimeError from exc
132
+
133
+ if executor_or_config is None or isinstance(executor_or_config, dict):
134
+ # Use default executor with context copied from current context
135
+ return await asyncio.get_running_loop().run_in_executor(
136
+ None,
137
+ functools.partial(copy_context().run, wrapper),
138
+ )
139
+
140
+ return await asyncio.get_running_loop().run_in_executor(executor_or_config, wrapper)
@@ -0,0 +1,220 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import concurrent.futures
5
+ import contextvars
6
+ import inspect
7
+ import sys
8
+ import types
9
+ from collections.abc import Awaitable, Coroutine, Generator
10
+ from typing import TypeVar, cast
11
+
12
+ T = TypeVar("T")
13
+ AnyFuture = asyncio.Future | concurrent.futures.Future
14
+
15
+ CONTEXT_NOT_SUPPORTED = sys.version_info < (3, 11)
16
+ EAGER_NOT_SUPPORTED = sys.version_info < (3, 12)
17
+
18
+
19
+ def _get_loop(fut: asyncio.Future) -> asyncio.AbstractEventLoop:
20
+ # Tries to call Future.get_loop() if it's available.
21
+ # Otherwise fallbacks to using the old '_loop' property.
22
+ try:
23
+ get_loop = fut.get_loop
24
+ except AttributeError:
25
+ pass
26
+ else:
27
+ return get_loop()
28
+ return fut._loop
29
+
30
+
31
+ def _convert_future_exc(exc: BaseException) -> BaseException:
32
+ exc_class = type(exc)
33
+ if exc_class is concurrent.futures.CancelledError:
34
+ return asyncio.CancelledError(*exc.args)
35
+ elif exc_class is concurrent.futures.TimeoutError:
36
+ return TimeoutError(*exc.args)
37
+ elif exc_class is concurrent.futures.InvalidStateError:
38
+ return asyncio.InvalidStateError(*exc.args)
39
+ else:
40
+ return exc
41
+
42
+
43
+ def _set_concurrent_future_state(
44
+ concurrent: concurrent.futures.Future,
45
+ source: AnyFuture,
46
+ ) -> None:
47
+ """Copy state from a future to a concurrent.futures.Future."""
48
+ assert source.done()
49
+ if source.cancelled():
50
+ concurrent.cancel()
51
+ if not concurrent.set_running_or_notify_cancel():
52
+ return
53
+ exception = source.exception()
54
+ if exception is not None:
55
+ concurrent.set_exception(_convert_future_exc(exception))
56
+ else:
57
+ result = source.result()
58
+ concurrent.set_result(result)
59
+
60
+
61
+ def _copy_future_state(source: AnyFuture, dest: asyncio.Future) -> None:
62
+ """Internal helper to copy state from another Future.
63
+
64
+ The other Future may be a concurrent.futures.Future.
65
+ """
66
+ if dest.done():
67
+ return
68
+ assert source.done()
69
+ if dest.cancelled():
70
+ return
71
+ if source.cancelled():
72
+ dest.cancel()
73
+ else:
74
+ exception = source.exception()
75
+ if exception is not None:
76
+ dest.set_exception(_convert_future_exc(exception))
77
+ else:
78
+ result = source.result()
79
+ dest.set_result(result)
80
+
81
+
82
+ def _chain_future(source: AnyFuture, destination: AnyFuture) -> None:
83
+ """Chain two futures so that when one completes, so does the other.
84
+
85
+ The result (or exception) of source will be copied to destination.
86
+ If destination is cancelled, source gets cancelled too.
87
+ Compatible with both asyncio.Future and concurrent.futures.Future.
88
+ """
89
+ if not asyncio.isfuture(source) and not isinstance(
90
+ source, concurrent.futures.Future
91
+ ):
92
+ raise TypeError("A future is required for source argument")
93
+ if not asyncio.isfuture(destination) and not isinstance(
94
+ destination, concurrent.futures.Future
95
+ ):
96
+ raise TypeError("A future is required for destination argument")
97
+ source_loop = _get_loop(source) if asyncio.isfuture(source) else None
98
+ dest_loop = _get_loop(destination) if asyncio.isfuture(destination) else None
99
+
100
+ def _set_state(future: AnyFuture, other: AnyFuture) -> None:
101
+ if asyncio.isfuture(future):
102
+ _copy_future_state(other, future)
103
+ else:
104
+ _set_concurrent_future_state(future, other)
105
+
106
+ def _call_check_cancel(destination: AnyFuture) -> None:
107
+ if destination.cancelled():
108
+ if source_loop is None or source_loop is dest_loop:
109
+ source.cancel()
110
+ else:
111
+ source_loop.call_soon_threadsafe(source.cancel)
112
+
113
+ def _call_set_state(source: AnyFuture) -> None:
114
+ if destination.cancelled() and dest_loop is not None and dest_loop.is_closed():
115
+ return
116
+ if dest_loop is None or dest_loop is source_loop:
117
+ _set_state(destination, source)
118
+ else:
119
+ if dest_loop.is_closed():
120
+ return
121
+ dest_loop.call_soon_threadsafe(_set_state, destination, source)
122
+
123
+ destination.add_done_callback(_call_check_cancel)
124
+ source.add_done_callback(_call_set_state)
125
+
126
+
127
+ def chain_future(source: AnyFuture, destination: AnyFuture) -> AnyFuture:
128
+ # adapted from asyncio.run_coroutine_threadsafe
129
+ try:
130
+ _chain_future(source, destination)
131
+ return destination
132
+ except (SystemExit, KeyboardInterrupt):
133
+ raise
134
+ except BaseException as exc:
135
+ if isinstance(destination, concurrent.futures.Future):
136
+ if destination.set_running_or_notify_cancel():
137
+ destination.set_exception(exc)
138
+ else:
139
+ destination.set_exception(exc)
140
+ raise
141
+
142
+
143
+ def _ensure_future(
144
+ coro_or_future: Coroutine[None, None, T] | Awaitable[T],
145
+ *,
146
+ loop: asyncio.AbstractEventLoop,
147
+ name: str | None = None,
148
+ context: contextvars.Context | None = None,
149
+ lazy: bool = True,
150
+ ) -> asyncio.Task[T]:
151
+ called_wrap_awaitable = False
152
+ if not asyncio.iscoroutine(coro_or_future):
153
+ if inspect.isawaitable(coro_or_future):
154
+ coro_or_future = cast(
155
+ Coroutine[None, None, T], _wrap_awaitable(coro_or_future)
156
+ )
157
+ called_wrap_awaitable = True
158
+ else:
159
+ raise TypeError(
160
+ "An asyncio.Future, a coroutine or an awaitable is required."
161
+ f" Got {type(coro_or_future).__name__} instead."
162
+ )
163
+
164
+ try:
165
+ if CONTEXT_NOT_SUPPORTED:
166
+ return loop.create_task(coro_or_future, name=name)
167
+ elif EAGER_NOT_SUPPORTED or lazy:
168
+ return loop.create_task(coro_or_future, name=name, context=context)
169
+ else:
170
+ return asyncio.eager_task_factory(
171
+ loop, coro_or_future, name=name, context=context
172
+ )
173
+ except RuntimeError:
174
+ if not called_wrap_awaitable:
175
+ coro_or_future.close()
176
+ raise
177
+
178
+
179
+ @types.coroutine
180
+ def _wrap_awaitable(awaitable: Awaitable[T]) -> Generator[None, None, T]:
181
+ """Helper for asyncio.ensure_future().
182
+
183
+ Wraps awaitable (an object with __await__) into a coroutine
184
+ that will later be wrapped in a Task by ensure_future().
185
+ """
186
+ return (yield from awaitable.__await__())
187
+
188
+
189
+ def run_coroutine_threadsafe(
190
+ coro: Coroutine[None, None, T],
191
+ loop: asyncio.AbstractEventLoop,
192
+ *,
193
+ lazy: bool,
194
+ name: str | None = None,
195
+ context: contextvars.Context | None = None,
196
+ ) -> asyncio.Future[T]:
197
+ """Submit a coroutine object to a given event loop.
198
+
199
+ Return an asyncio.Future to access the result.
200
+ """
201
+
202
+ if asyncio._get_running_loop() is loop:
203
+ return _ensure_future(coro, loop=loop, name=name, context=context, lazy=lazy)
204
+ else:
205
+ future: asyncio.Future[T] = asyncio.Future(loop=loop)
206
+
207
+ def callback() -> None:
208
+ try:
209
+ chain_future(
210
+ _ensure_future(coro, loop=loop, name=name, context=context),
211
+ future,
212
+ )
213
+ except (SystemExit, KeyboardInterrupt):
214
+ raise
215
+ except BaseException as exc:
216
+ future.set_exception(exc)
217
+ raise
218
+
219
+ loop.call_soon_threadsafe(callback, context=context)
220
+ return future
File without changes
@@ -1 +0,0 @@
1
- __version__ = "0.2.72"
File without changes
File without changes
File without changes
File without changes