maton-agent-toolkit 0.1.0__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.
- maton_agent_toolkit/__init__.py +7 -0
- maton_agent_toolkit/configuration.py +14 -0
- maton_agent_toolkit/crewai/__init__.py +5 -0
- maton_agent_toolkit/crewai/toolkit.py +134 -0
- maton_agent_toolkit/langchain/__init__.py +5 -0
- maton_agent_toolkit/langchain/toolkit.py +134 -0
- maton_agent_toolkit/openai/__init__.py +5 -0
- maton_agent_toolkit/openai/toolkit.py +125 -0
- maton_agent_toolkit/shared/__init__.py +21 -0
- maton_agent_toolkit/shared/async_initializer.py +71 -0
- maton_agent_toolkit/shared/constants.py +6 -0
- maton_agent_toolkit/shared/mcp_client.py +231 -0
- maton_agent_toolkit/shared/schema_utils.py +127 -0
- maton_agent_toolkit/shared/toolkit_core.py +163 -0
- maton_agent_toolkit/strands/__init__.py +5 -0
- maton_agent_toolkit/strands/toolkit.py +173 -0
- maton_agent_toolkit-0.1.0.dist-info/METADATA +135 -0
- maton_agent_toolkit-0.1.0.dist-info/RECORD +21 -0
- maton_agent_toolkit-0.1.0.dist-info/WHEEL +5 -0
- maton_agent_toolkit-0.1.0.dist-info/licenses/LICENSE +23 -0
- maton_agent_toolkit-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Configuration types for Maton Agent Toolkit."""
|
|
2
|
+
|
|
3
|
+
from typing_extensions import TypedDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Context(TypedDict, total=False):
|
|
7
|
+
"""Context for MCP connection."""
|
|
8
|
+
connection: str | None
|
|
9
|
+
mode: str | None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Configuration(TypedDict, total=False):
|
|
13
|
+
"""Configuration for Maton Agent Toolkit."""
|
|
14
|
+
context: Context | None
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Maton Agent Toolkit for CrewAI."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import List, Any, Type, Callable, Awaitable
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from crewai.tools import BaseTool
|
|
8
|
+
|
|
9
|
+
from ..shared.toolkit_core import ToolkitCore
|
|
10
|
+
from ..shared.mcp_client import McpTool
|
|
11
|
+
from ..shared.schema_utils import json_schema_to_pydantic_model
|
|
12
|
+
from ..configuration import Configuration
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MatonTool(BaseTool):
|
|
16
|
+
"""Tool for interacting with Maton via MCP."""
|
|
17
|
+
|
|
18
|
+
run_tool: Callable[..., Awaitable[str]]
|
|
19
|
+
method: str
|
|
20
|
+
name: str = ""
|
|
21
|
+
description: str = ""
|
|
22
|
+
args_schema: Type[BaseModel] | None = None
|
|
23
|
+
|
|
24
|
+
def _run(self, **kwargs: Any) -> str:
|
|
25
|
+
"""Synchronous execution - wraps async call."""
|
|
26
|
+
loop = asyncio.get_event_loop()
|
|
27
|
+
if loop.is_running():
|
|
28
|
+
# If we're already in an async context, create a new loop
|
|
29
|
+
import concurrent.futures
|
|
30
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
31
|
+
future = pool.submit(
|
|
32
|
+
asyncio.run,
|
|
33
|
+
self.run_tool(self.method, kwargs)
|
|
34
|
+
)
|
|
35
|
+
return future.result()
|
|
36
|
+
else:
|
|
37
|
+
return loop.run_until_complete(
|
|
38
|
+
self.run_tool(self.method, kwargs)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def _arun(self, **kwargs: Any) -> str:
|
|
42
|
+
"""Async execution via MCP."""
|
|
43
|
+
return await self.run_tool(self.method, kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MatonAgentToolkit(ToolkitCore[List[MatonTool]]):
|
|
47
|
+
"""
|
|
48
|
+
Maton Agent Toolkit for CrewAI.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
toolkit = await create_maton_agent_toolkit(
|
|
52
|
+
api_key='YOUR_MATON_API_KEY',
|
|
53
|
+
)
|
|
54
|
+
tools = toolkit.get_tools()
|
|
55
|
+
agent = Agent(role="...", tools=tools)
|
|
56
|
+
await toolkit.close()
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str | None = None,
|
|
62
|
+
configuration: Configuration | None = None
|
|
63
|
+
):
|
|
64
|
+
super().__init__(api_key, configuration)
|
|
65
|
+
|
|
66
|
+
def _empty_tools(self) -> List[MatonTool]:
|
|
67
|
+
"""Return empty list of tools."""
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
def _convert_tools(
|
|
71
|
+
self,
|
|
72
|
+
mcp_tools: List[McpTool]
|
|
73
|
+
) -> List[MatonTool]:
|
|
74
|
+
"""Convert MCP tools to CrewAI MatonTool instances."""
|
|
75
|
+
tools = []
|
|
76
|
+
for mcp_tool in mcp_tools:
|
|
77
|
+
# Convert JSON Schema to Pydantic model
|
|
78
|
+
args_schema = json_schema_to_pydantic_model(
|
|
79
|
+
mcp_tool.get("inputSchema"),
|
|
80
|
+
model_name=f"{mcp_tool['name']}_args"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
tools.append(MatonTool(
|
|
84
|
+
run_tool=self.run_tool,
|
|
85
|
+
method=mcp_tool["name"],
|
|
86
|
+
name=mcp_tool["name"],
|
|
87
|
+
description=mcp_tool.get("description", mcp_tool["name"]),
|
|
88
|
+
args_schema=args_schema,
|
|
89
|
+
))
|
|
90
|
+
return tools
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def tools(self) -> List[MatonTool]:
|
|
94
|
+
"""
|
|
95
|
+
The tools available in the toolkit.
|
|
96
|
+
|
|
97
|
+
.. deprecated::
|
|
98
|
+
Access tools via get_tools() after calling initialize().
|
|
99
|
+
"""
|
|
100
|
+
return self._get_tools_with_warning()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def create_maton_agent_toolkit(
|
|
104
|
+
api_key: str | None = None,
|
|
105
|
+
configuration: Configuration | None = None
|
|
106
|
+
) -> MatonAgentToolkit:
|
|
107
|
+
"""
|
|
108
|
+
Factory function to create and initialize a MatonAgentToolkit.
|
|
109
|
+
|
|
110
|
+
This is the recommended way to create a toolkit as it handles
|
|
111
|
+
async initialization automatically.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
toolkit = await create_maton_agent_toolkit(
|
|
115
|
+
api_key='YOUR_MATON_API_KEY',
|
|
116
|
+
)
|
|
117
|
+
tools = toolkit.get_tools()
|
|
118
|
+
|
|
119
|
+
# Use with CrewAI agent
|
|
120
|
+
agent = Agent(role="Maton Agent", tools=tools)
|
|
121
|
+
|
|
122
|
+
# Clean up when done
|
|
123
|
+
await toolkit.close()
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
api_key: Maton API key. Create one at https://maton.ai
|
|
127
|
+
configuration: Optional configuration for context
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Initialized MatonAgentToolkit ready to use
|
|
131
|
+
"""
|
|
132
|
+
toolkit = MatonAgentToolkit(api_key, configuration)
|
|
133
|
+
await toolkit.initialize()
|
|
134
|
+
return toolkit
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Maton Agent Toolkit for LangChain."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import List, Any, Type, Callable, Awaitable
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
from langchain_core.tools import BaseTool
|
|
8
|
+
|
|
9
|
+
from ..shared.toolkit_core import ToolkitCore
|
|
10
|
+
from ..shared.mcp_client import McpTool
|
|
11
|
+
from ..shared.schema_utils import json_schema_to_pydantic_model
|
|
12
|
+
from ..configuration import Configuration
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class MatonTool(BaseTool):
|
|
16
|
+
"""Tool for interacting with Maton via MCP."""
|
|
17
|
+
|
|
18
|
+
run_tool: Callable[..., Awaitable[str]]
|
|
19
|
+
method: str
|
|
20
|
+
name: str = ""
|
|
21
|
+
description: str = ""
|
|
22
|
+
args_schema: Type[BaseModel] | None = None
|
|
23
|
+
|
|
24
|
+
def _run(self, **kwargs: Any) -> str:
|
|
25
|
+
"""Synchronous execution - wraps async call."""
|
|
26
|
+
loop = asyncio.get_event_loop()
|
|
27
|
+
if loop.is_running():
|
|
28
|
+
# If we're already in an async context, create a new loop
|
|
29
|
+
import concurrent.futures
|
|
30
|
+
with concurrent.futures.ThreadPoolExecutor() as pool:
|
|
31
|
+
future = pool.submit(
|
|
32
|
+
asyncio.run,
|
|
33
|
+
self.run_tool(self.method, kwargs)
|
|
34
|
+
)
|
|
35
|
+
return future.result()
|
|
36
|
+
else:
|
|
37
|
+
return loop.run_until_complete(
|
|
38
|
+
self.run_tool(self.method, kwargs)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
async def _arun(self, **kwargs: Any) -> str:
|
|
42
|
+
"""Async execution via MCP."""
|
|
43
|
+
return await self.run_tool(self.method, kwargs)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class MatonAgentToolkit(ToolkitCore[List[MatonTool]]):
|
|
47
|
+
"""
|
|
48
|
+
Maton Agent Toolkit for LangChain.
|
|
49
|
+
|
|
50
|
+
Example:
|
|
51
|
+
toolkit = await create_maton_agent_toolkit(
|
|
52
|
+
api_key='YOUR_MATON_API_KEY',
|
|
53
|
+
)
|
|
54
|
+
tools = toolkit.get_tools()
|
|
55
|
+
agent = create_react_agent(llm, tools, prompt)
|
|
56
|
+
await toolkit.close()
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_key: str | None = None,
|
|
62
|
+
configuration: Configuration | None = None
|
|
63
|
+
):
|
|
64
|
+
super().__init__(api_key, configuration)
|
|
65
|
+
|
|
66
|
+
def _empty_tools(self) -> List[MatonTool]:
|
|
67
|
+
"""Return empty list of tools."""
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
def _convert_tools(
|
|
71
|
+
self,
|
|
72
|
+
mcp_tools: List[McpTool]
|
|
73
|
+
) -> List[MatonTool]:
|
|
74
|
+
"""Convert MCP tools to LangChain MatonTool instances."""
|
|
75
|
+
tools = []
|
|
76
|
+
for mcp_tool in mcp_tools:
|
|
77
|
+
# Convert JSON Schema to Pydantic model
|
|
78
|
+
args_schema = json_schema_to_pydantic_model(
|
|
79
|
+
mcp_tool.get("inputSchema"),
|
|
80
|
+
model_name=f"{mcp_tool['name']}_args"
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
tools.append(MatonTool(
|
|
84
|
+
run_tool=self.run_tool,
|
|
85
|
+
method=mcp_tool["name"],
|
|
86
|
+
name=mcp_tool["name"],
|
|
87
|
+
description=mcp_tool.get("description", mcp_tool["name"]),
|
|
88
|
+
args_schema=args_schema,
|
|
89
|
+
))
|
|
90
|
+
return tools
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def tools(self) -> List[MatonTool]:
|
|
94
|
+
"""
|
|
95
|
+
The tools available in the toolkit.
|
|
96
|
+
|
|
97
|
+
.. deprecated::
|
|
98
|
+
Access tools via get_tools() after calling initialize().
|
|
99
|
+
"""
|
|
100
|
+
return self._get_tools_with_warning()
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
async def create_maton_agent_toolkit(
|
|
104
|
+
api_key: str | None = None,
|
|
105
|
+
configuration: Configuration | None = None
|
|
106
|
+
) -> MatonAgentToolkit:
|
|
107
|
+
"""
|
|
108
|
+
Factory function to create and initialize a MatonAgentToolkit.
|
|
109
|
+
|
|
110
|
+
This is the recommended way to create a toolkit as it handles
|
|
111
|
+
async initialization automatically.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
toolkit = await create_maton_agent_toolkit(
|
|
115
|
+
api_key='YOUR_MATON_API_KEY',
|
|
116
|
+
)
|
|
117
|
+
tools = toolkit.get_tools()
|
|
118
|
+
|
|
119
|
+
# Use with LangChain agent
|
|
120
|
+
agent = create_react_agent(llm, tools, prompt)
|
|
121
|
+
|
|
122
|
+
# Clean up when done
|
|
123
|
+
await toolkit.close()
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
api_key: Maton API key. Create one at https://maton.ai
|
|
127
|
+
configuration: Optional configuration for context
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Initialized MatonAgentToolkit ready to use
|
|
131
|
+
"""
|
|
132
|
+
toolkit = MatonAgentToolkit(api_key, configuration)
|
|
133
|
+
await toolkit.initialize()
|
|
134
|
+
return toolkit
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Maton Agent Toolkit for OpenAI Agents SDK."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import List, Any
|
|
5
|
+
|
|
6
|
+
from agents import FunctionTool
|
|
7
|
+
from agents.run_context import RunContextWrapper
|
|
8
|
+
|
|
9
|
+
from ..shared.toolkit_core import ToolkitCore
|
|
10
|
+
from ..shared.mcp_client import McpTool
|
|
11
|
+
from ..configuration import Configuration
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MatonAgentToolkit(ToolkitCore[List[FunctionTool]]):
|
|
15
|
+
"""
|
|
16
|
+
Maton Agent Toolkit for OpenAI Agents SDK.
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
toolkit = await create_maton_agent_toolkit(
|
|
20
|
+
api_key='YOUR_MATON_API_KEY',
|
|
21
|
+
)
|
|
22
|
+
tools = toolkit.get_tools()
|
|
23
|
+
agent = Agent(name="Maton Agent", tools=tools)
|
|
24
|
+
await toolkit.close()
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(
|
|
28
|
+
self,
|
|
29
|
+
api_key: str | None = None,
|
|
30
|
+
configuration: Configuration | None = None
|
|
31
|
+
):
|
|
32
|
+
super().__init__(api_key, configuration)
|
|
33
|
+
|
|
34
|
+
def _empty_tools(self) -> List[FunctionTool]:
|
|
35
|
+
"""Return empty list of tools."""
|
|
36
|
+
return []
|
|
37
|
+
|
|
38
|
+
def _convert_tools(
|
|
39
|
+
self,
|
|
40
|
+
mcp_tools: List[McpTool]
|
|
41
|
+
) -> List[FunctionTool]:
|
|
42
|
+
"""Convert MCP tools to OpenAI FunctionTool instances."""
|
|
43
|
+
tools = []
|
|
44
|
+
for mcp_tool in mcp_tools:
|
|
45
|
+
tools.append(self._create_function_tool(mcp_tool))
|
|
46
|
+
return tools
|
|
47
|
+
|
|
48
|
+
def _create_function_tool(self, mcp_tool: McpTool) -> FunctionTool:
|
|
49
|
+
"""Create a FunctionTool from an MCP tool definition."""
|
|
50
|
+
toolkit = self
|
|
51
|
+
tool_name = mcp_tool["name"]
|
|
52
|
+
|
|
53
|
+
async def on_invoke_tool(
|
|
54
|
+
ctx: RunContextWrapper[Any],
|
|
55
|
+
input_str: str
|
|
56
|
+
) -> str:
|
|
57
|
+
args = json.loads(input_str)
|
|
58
|
+
return await toolkit.run_tool(tool_name, args)
|
|
59
|
+
|
|
60
|
+
# Prepare parameters schema
|
|
61
|
+
parameters = dict(mcp_tool.get("inputSchema", {}))
|
|
62
|
+
parameters["additionalProperties"] = False
|
|
63
|
+
parameters["type"] = "object"
|
|
64
|
+
|
|
65
|
+
# Clean up schema - remove fields not needed in OpenAI function schema
|
|
66
|
+
for key in ["description", "title"]:
|
|
67
|
+
parameters.pop(key, None)
|
|
68
|
+
|
|
69
|
+
if "properties" in parameters:
|
|
70
|
+
for prop in parameters["properties"].values():
|
|
71
|
+
for key in ["title", "default"]:
|
|
72
|
+
if isinstance(prop, dict):
|
|
73
|
+
prop.pop(key, None)
|
|
74
|
+
|
|
75
|
+
return FunctionTool(
|
|
76
|
+
name=tool_name,
|
|
77
|
+
description=mcp_tool.get("description", tool_name),
|
|
78
|
+
params_json_schema=parameters,
|
|
79
|
+
on_invoke_tool=on_invoke_tool,
|
|
80
|
+
strict_json_schema=False
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def tools(self) -> List[FunctionTool]:
|
|
85
|
+
"""
|
|
86
|
+
The tools available in the toolkit.
|
|
87
|
+
|
|
88
|
+
.. deprecated::
|
|
89
|
+
Access tools via get_tools() after calling initialize().
|
|
90
|
+
"""
|
|
91
|
+
return self._get_tools_with_warning()
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
async def create_maton_agent_toolkit(
|
|
95
|
+
api_key: str | None = None,
|
|
96
|
+
configuration: Configuration | None = None
|
|
97
|
+
) -> MatonAgentToolkit:
|
|
98
|
+
"""
|
|
99
|
+
Factory function to create and initialize a MatonAgentToolkit.
|
|
100
|
+
|
|
101
|
+
This is the recommended way to create a toolkit as it handles
|
|
102
|
+
async initialization automatically.
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
toolkit = await create_maton_agent_toolkit(
|
|
106
|
+
api_key='YOUR_MATON_API_KEY',
|
|
107
|
+
)
|
|
108
|
+
tools = toolkit.get_tools()
|
|
109
|
+
|
|
110
|
+
# Use with agent
|
|
111
|
+
agent = Agent(name="Maton Agent", tools=tools)
|
|
112
|
+
|
|
113
|
+
# Clean up when done
|
|
114
|
+
await toolkit.close()
|
|
115
|
+
|
|
116
|
+
Args:
|
|
117
|
+
api_key: Maton API key. Create one at https://maton.ai
|
|
118
|
+
configuration: Optional configuration for context
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Initialized MatonAgentToolkit ready to use
|
|
122
|
+
"""
|
|
123
|
+
toolkit = MatonAgentToolkit(api_key, configuration)
|
|
124
|
+
await toolkit.initialize()
|
|
125
|
+
return toolkit
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Shared infrastructure for Maton Agent Toolkit MCP integration."""
|
|
2
|
+
|
|
3
|
+
from .constants import VERSION, MCP_SERVER_URL, TOOLKIT_HEADER, MCP_HEADER
|
|
4
|
+
from .async_initializer import AsyncInitializer
|
|
5
|
+
from .mcp_client import MatonMcpClient, McpTool, McpToolInputSchema
|
|
6
|
+
from .schema_utils import json_schema_to_pydantic_model, json_schema_to_pydantic_fields
|
|
7
|
+
from .toolkit_core import ToolkitCore
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"VERSION",
|
|
11
|
+
"MCP_SERVER_URL",
|
|
12
|
+
"TOOLKIT_HEADER",
|
|
13
|
+
"MCP_HEADER",
|
|
14
|
+
"AsyncInitializer",
|
|
15
|
+
"MatonMcpClient",
|
|
16
|
+
"McpTool",
|
|
17
|
+
"McpToolInputSchema",
|
|
18
|
+
"json_schema_to_pydantic_model",
|
|
19
|
+
"json_schema_to_pydantic_fields",
|
|
20
|
+
"ToolkitCore",
|
|
21
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Async initialization utility for Maton Agent Toolkit."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Callable, Awaitable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AsyncInitializer:
|
|
8
|
+
"""
|
|
9
|
+
A reusable utility for async initialization with future locking.
|
|
10
|
+
Ensures initialization runs only once, even if called concurrently.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self) -> None:
|
|
14
|
+
self._initialized: bool = False
|
|
15
|
+
self._init_future: asyncio.Future[None] | None = None
|
|
16
|
+
self._lock: asyncio.Lock = asyncio.Lock()
|
|
17
|
+
|
|
18
|
+
async def initialize(
|
|
19
|
+
self, do_initialize: Callable[[], Awaitable[None]]
|
|
20
|
+
) -> None:
|
|
21
|
+
"""
|
|
22
|
+
Initialize using the provided coroutine function.
|
|
23
|
+
|
|
24
|
+
- If already initialized, returns immediately
|
|
25
|
+
- If initialization in progress, awaits existing future
|
|
26
|
+
- If initialization fails, allows retry on next call
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
do_initialize: Async function that performs the actual
|
|
30
|
+
initialization work.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
Exception: Re-raises any exception from do_initialize,
|
|
34
|
+
allowing retry on next call.
|
|
35
|
+
"""
|
|
36
|
+
if self._initialized:
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
async with self._lock:
|
|
40
|
+
# Double-check after acquiring lock
|
|
41
|
+
if self._initialized:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
if self._init_future is not None:
|
|
45
|
+
# Another coroutine is initializing, wait for it
|
|
46
|
+
await self._init_future
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
# Create a new future for this initialization attempt
|
|
50
|
+
loop = asyncio.get_running_loop()
|
|
51
|
+
self._init_future = loop.create_future()
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
await do_initialize()
|
|
55
|
+
self._initialized = True
|
|
56
|
+
self._init_future.set_result(None)
|
|
57
|
+
except Exception as e:
|
|
58
|
+
# Reset future on failure to allow retry
|
|
59
|
+
self._init_future.set_exception(e)
|
|
60
|
+
self._init_future = None
|
|
61
|
+
raise
|
|
62
|
+
|
|
63
|
+
@property
|
|
64
|
+
def is_initialized(self) -> bool:
|
|
65
|
+
"""Check if initialization has completed successfully."""
|
|
66
|
+
return self._initialized
|
|
67
|
+
|
|
68
|
+
def reset(self) -> None:
|
|
69
|
+
"""Reset the initializer state. Used during close/cleanup."""
|
|
70
|
+
self._initialized = False
|
|
71
|
+
self._init_future = None
|