idun-agent-engine 0.1.0__py3-none-any.whl → 0.2.2__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.
- idun_agent_engine/__init__.py +2 -25
- idun_agent_engine/_version.py +1 -1
- idun_agent_engine/agent/__init__.py +10 -0
- idun_agent_engine/agent/base.py +97 -0
- idun_agent_engine/agent/haystack/__init__.py +9 -0
- idun_agent_engine/agent/haystack/haystack.py +261 -0
- idun_agent_engine/agent/haystack/haystack_model.py +13 -0
- idun_agent_engine/agent/haystack/utils.py +13 -0
- idun_agent_engine/agent/langgraph/__init__.py +7 -0
- idun_agent_engine/agent/langgraph/langgraph.py +431 -0
- idun_agent_engine/cli/__init__.py +16 -0
- idun_agent_engine/core/__init__.py +11 -0
- idun_agent_engine/core/app_factory.py +63 -0
- idun_agent_engine/core/config_builder.py +456 -0
- idun_agent_engine/core/engine_config.py +22 -0
- idun_agent_engine/core/server_runner.py +146 -0
- idun_agent_engine/observability/__init__.py +13 -0
- idun_agent_engine/observability/base.py +111 -0
- idun_agent_engine/observability/langfuse/__init__.py +5 -0
- idun_agent_engine/observability/langfuse/langfuse_handler.py +72 -0
- idun_agent_engine/observability/phoenix/__init__.py +5 -0
- idun_agent_engine/observability/phoenix/phoenix_handler.py +65 -0
- idun_agent_engine/observability/phoenix_local/__init__.py +5 -0
- idun_agent_engine/observability/phoenix_local/phoenix_local_handler.py +123 -0
- idun_agent_engine/py.typed +0 -1
- idun_agent_engine/server/__init__.py +5 -0
- idun_agent_engine/server/dependencies.py +23 -0
- idun_agent_engine/server/lifespan.py +42 -0
- idun_agent_engine/server/routers/__init__.py +5 -0
- idun_agent_engine/server/routers/agent.py +68 -0
- idun_agent_engine/server/routers/base.py +60 -0
- idun_agent_engine/server/server_config.py +8 -0
- idun_agent_engine-0.2.2.dist-info/METADATA +281 -0
- idun_agent_engine-0.2.2.dist-info/RECORD +43 -0
- {idun_agent_engine-0.1.0.dist-info → idun_agent_engine-0.2.2.dist-info}/WHEEL +1 -1
- idun_agent_engine-0.2.2.dist-info/entry_points.txt +2 -0
- idun_platform_cli/__init__.py +0 -0
- idun_platform_cli/groups/__init__.py +0 -0
- idun_platform_cli/groups/agent/__init__.py +0 -0
- idun_platform_cli/groups/agent/main.py +16 -0
- idun_platform_cli/groups/agent/package.py +73 -0
- idun_platform_cli/groups/agent/serve.py +104 -0
- idun_platform_cli/main.py +14 -0
- idun_agent_engine-0.1.0.dist-info/METADATA +0 -317
- idun_agent_engine-0.1.0.dist-info/RECORD +0 -6
idun_agent_engine/__init__.py
CHANGED
|
@@ -1,30 +1,8 @@
|
|
|
1
|
-
"""Idun Agent Engine
|
|
1
|
+
"""Idun Agent Engine public API.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
and automatically generates a production-ready FastAPI server for your agents.
|
|
5
|
-
|
|
6
|
-
Quick Start:
|
|
7
|
-
from idun_agent_engine import ConfigBuilder, create_app, run_server
|
|
8
|
-
|
|
9
|
-
# Method 1: Using ConfigBuilder (Recommended)
|
|
10
|
-
config = (ConfigBuilder()
|
|
11
|
-
.with_langgraph_agent(name="My Agent", graph_definition="agent.py:graph")
|
|
12
|
-
.build())
|
|
13
|
-
app = create_app(engine_config=config)
|
|
14
|
-
run_server(app)
|
|
15
|
-
|
|
16
|
-
# Method 2: Using YAML config file
|
|
17
|
-
app = create_app(config_path="config.yaml")
|
|
18
|
-
run_server(app, port=8000)
|
|
19
|
-
|
|
20
|
-
# Method 3: One-liner from config file
|
|
21
|
-
from idun_agent_engine.core.server_runner import run_server_from_config
|
|
22
|
-
run_server_from_config("config.yaml")
|
|
23
|
-
|
|
24
|
-
For more advanced usage, see the documentation.
|
|
3
|
+
Exports top-level helpers for convenience imports in examples and user code.
|
|
25
4
|
"""
|
|
26
5
|
|
|
27
|
-
# Version information - import from separate module to avoid circular imports
|
|
28
6
|
from ._version import __version__
|
|
29
7
|
from .agent.base import BaseAgent
|
|
30
8
|
from .core.app_factory import create_app
|
|
@@ -35,7 +13,6 @@ from .core.server_runner import (
|
|
|
35
13
|
run_server_from_config,
|
|
36
14
|
)
|
|
37
15
|
|
|
38
|
-
# Main public API
|
|
39
16
|
__all__ = [
|
|
40
17
|
"create_app",
|
|
41
18
|
"run_server",
|
idun_agent_engine/_version.py
CHANGED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Agent base interfaces.
|
|
2
|
+
|
|
3
|
+
Defines the abstract `BaseAgent` used by all agent implementations.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
from collections.abc import AsyncGenerator
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from idun_agent_schema.engine.agent import BaseAgentConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseAgent[ConfigType: BaseAgentConfig](ABC):
|
|
14
|
+
"""Abstract base for agents pluggable into the Idun Agent Engine.
|
|
15
|
+
|
|
16
|
+
Implements the public protocol that concrete agent adapters must follow.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_configuration: ConfigType
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
@abstractmethod
|
|
23
|
+
def id(self) -> str:
|
|
24
|
+
"""Unique identifier for the agent instance."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def agent_type(self) -> str:
|
|
30
|
+
"""Type or category of the agent (e.g., 'LangGraph', 'ADK')."""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def agent_instance(self) -> Any:
|
|
36
|
+
"""Get the underlying agent instance from the specific framework.
|
|
37
|
+
|
|
38
|
+
This might be set after initialization.
|
|
39
|
+
"""
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def configuration(self) -> ConfigType:
|
|
44
|
+
"""Return current configuration settings for the agent.
|
|
45
|
+
|
|
46
|
+
This is typically the configuration used during initialization.
|
|
47
|
+
"""
|
|
48
|
+
return self._configuration
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
@abstractmethod
|
|
52
|
+
def infos(self) -> dict[str, Any]:
|
|
53
|
+
"""General information about the agent instance (e.g., version, status, metadata)."""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
@abstractmethod
|
|
57
|
+
async def initialize(self, config: dict[str, Any]) -> None:
|
|
58
|
+
"""Initialize the agent with a given configuration.
|
|
59
|
+
|
|
60
|
+
This method should set up the underlying agent framework instance.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
config: A dictionary containing the agent's configuration.
|
|
64
|
+
"""
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
@abstractmethod
|
|
68
|
+
async def invoke(self, message: Any) -> Any:
|
|
69
|
+
"""Process a single input message and return a response.
|
|
70
|
+
|
|
71
|
+
This should be an awaitable method if the underlying agent processes
|
|
72
|
+
asynchronously.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
message: The input message for the agent.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
The agent's response.
|
|
79
|
+
"""
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
@abstractmethod
|
|
83
|
+
async def stream(self, message: Any) -> AsyncGenerator[Any]:
|
|
84
|
+
"""Process a single input message and return an asynchronous stream.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
message: The input message for the agent.
|
|
88
|
+
|
|
89
|
+
Yields:
|
|
90
|
+
Chunks of the agent's response.
|
|
91
|
+
"""
|
|
92
|
+
# This is an async generator, so it needs `async def` and `yield`
|
|
93
|
+
# For the ABC, we can't have a `yield` directly in the abstract method body.
|
|
94
|
+
# The signature itself defines it as an async generator.
|
|
95
|
+
# Example: async for chunk in agent.stream(message): ...
|
|
96
|
+
if False: # pragma: no cover (This is just to make it a generator type for static analysis)
|
|
97
|
+
yield
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import importlib.util
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import uuid
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
os.environ["HAYSTACK_CONTENT_TRACING_ENABLED"] = "true"
|
|
8
|
+
|
|
9
|
+
from haystack import Pipeline
|
|
10
|
+
from haystack.components.agents import Agent
|
|
11
|
+
from haystack.dataclasses import ChatMessage
|
|
12
|
+
from haystack_integrations.components.connectors.langfuse import LangfuseConnector
|
|
13
|
+
|
|
14
|
+
from idun_agent_engine.agent.base import BaseAgent
|
|
15
|
+
from idun_agent_schema.engine.haystack import HaystackAgentConfig
|
|
16
|
+
from idun_agent_engine.agent.haystack.utils import _parse_component_definition
|
|
17
|
+
|
|
18
|
+
logging.basicConfig(
|
|
19
|
+
format="%(asctime)s %(levelname)-8s %(message)s",
|
|
20
|
+
level=logging.INFO,
|
|
21
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HaystackAgent(BaseAgent):
|
|
28
|
+
"""Haystack agent adapter implementing the BaseAgent protocol."""
|
|
29
|
+
|
|
30
|
+
def __init__(self):
|
|
31
|
+
"""Initialize an unconfigured haystack agent with default state."""
|
|
32
|
+
self._id: str = str(uuid.uuid4())
|
|
33
|
+
self._agent_type: str = "haystack"
|
|
34
|
+
self._agent_instance: Any = None
|
|
35
|
+
self._configuration: HaystackAgentConfig | None = None
|
|
36
|
+
self._name: str = "Haystack Agent"
|
|
37
|
+
self._langfuse_tracing: bool = False
|
|
38
|
+
self._enable_tracing: bool = False
|
|
39
|
+
self._infos: dict[str, Any] = {
|
|
40
|
+
"status": "Uninitialized",
|
|
41
|
+
"name": self._name,
|
|
42
|
+
"id": self._id,
|
|
43
|
+
}
|
|
44
|
+
# TODO: input/output schema
|
|
45
|
+
# TODO: checkpointing/debugging
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def id(self) -> str:
|
|
49
|
+
"""Returns the agent id."""
|
|
50
|
+
return self._id
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def agent_type(self) -> str:
|
|
54
|
+
"""Return agent type label."""
|
|
55
|
+
return self._agent_type
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def name(self) -> str:
|
|
59
|
+
"""Return configured human-readable agent name."""
|
|
60
|
+
return self._name
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def agent_instance(self) -> Any:
|
|
64
|
+
"""Return compiled graph instance.
|
|
65
|
+
|
|
66
|
+
Raises:
|
|
67
|
+
RuntimeError: If the agent is not yet initialized.
|
|
68
|
+
"""
|
|
69
|
+
if self._agent_instance is None:
|
|
70
|
+
raise RuntimeError("Agent not initialized. Call initialize() first.")
|
|
71
|
+
return self._agent_instance
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def configuration(self) -> HaystackAgentConfig:
|
|
75
|
+
"""Return validated configuration.
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
RuntimeError: If the agent has not been configured yet.
|
|
79
|
+
"""
|
|
80
|
+
if not self._configuration:
|
|
81
|
+
raise RuntimeError("Agent not configured. Call initialize() first.")
|
|
82
|
+
return self._configuration
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def infos(self) -> dict[str, Any]:
|
|
86
|
+
"""Return diagnostic information about the agent instance."""
|
|
87
|
+
return self._infos
|
|
88
|
+
|
|
89
|
+
def _check_langfuse_tracing(self, pipeline: Pipeline) -> None:
|
|
90
|
+
"""Check if the pipeline has a LangfuseConnector."""
|
|
91
|
+
logger.debug("Searching LangfuseConnector in the pipeline..")
|
|
92
|
+
for name, component in pipeline.walk():
|
|
93
|
+
if isinstance(component, LangfuseConnector):
|
|
94
|
+
logger.info(f"Found LangfuseConnector component with name: {name}")
|
|
95
|
+
self._langfuse_tracing = True
|
|
96
|
+
|
|
97
|
+
def _add_langfuse_tracing(self, component: Agent | Pipeline):
|
|
98
|
+
logger.debug("Checking for Langfuse tracing...")
|
|
99
|
+
if isinstance(component, Pipeline):
|
|
100
|
+
if self._langfuse_tracing:
|
|
101
|
+
logger.info("langfuse tracing already on")
|
|
102
|
+
elif not self._langfuse_tracing and self._enable_tracing:
|
|
103
|
+
logger.info("Pipeline has no tracer included. Adding Langfuse tracer")
|
|
104
|
+
if (
|
|
105
|
+
not os.environ.get("LANGFUSE_API_KEY")
|
|
106
|
+
or not os.environ.get("LANGFUSE_SECRET_KEY")
|
|
107
|
+
or not os.environ.get("LANGFUSE_PUBLIC_KEY")
|
|
108
|
+
):
|
|
109
|
+
raise ValueError(
|
|
110
|
+
"Langfuse keys not set! make sure you set Langfuse secret and public keys"
|
|
111
|
+
)
|
|
112
|
+
component.add_component(
|
|
113
|
+
f"{self._configuration.name} tracer",
|
|
114
|
+
instance=LangfuseConnector(self._configuration.name),
|
|
115
|
+
)
|
|
116
|
+
logger.info("Added component tracer")
|
|
117
|
+
self._langfuse_tracing = True
|
|
118
|
+
logger.info("Agent tracing not supported yet")
|
|
119
|
+
|
|
120
|
+
async def initialize(self, config: HaystackAgentConfig | dict[str, Any]) -> None:
|
|
121
|
+
try:
|
|
122
|
+
logger.debug(f"Initializing haystack agent config: {config}...")
|
|
123
|
+
|
|
124
|
+
if isinstance(config, HaystackAgentConfig):
|
|
125
|
+
self._configuration = config
|
|
126
|
+
logger.debug("Validated HaystackAgentConfig")
|
|
127
|
+
else:
|
|
128
|
+
logger.warning(f"Validating a dict config: {config}")
|
|
129
|
+
self._configuration = HaystackAgentConfig.model_validate(config)
|
|
130
|
+
logger.debug("Validated dict config")
|
|
131
|
+
self._name = self._configuration.name or "Haystack Agent"
|
|
132
|
+
self._infos["name"] = self._name
|
|
133
|
+
# TODO: await persistence haystack
|
|
134
|
+
# TODO OBS block
|
|
135
|
+
|
|
136
|
+
# check if config has observability `enabled` or `disabled`, so that we adjust our component to
|
|
137
|
+
# either add a tracer or not
|
|
138
|
+
if self._configuration.observability.enabled:
|
|
139
|
+
self._enable_tracing = True
|
|
140
|
+
logger.info("Enabling tracing...")
|
|
141
|
+
component: Agent | Pipeline = self._load_component(
|
|
142
|
+
self._configuration.component_definition
|
|
143
|
+
)
|
|
144
|
+
self._infos["component_type"] = self._configuration.component_type
|
|
145
|
+
self._infos["component_definition"] = (
|
|
146
|
+
self._configuration.component_definition
|
|
147
|
+
)
|
|
148
|
+
self._agent_instance = component
|
|
149
|
+
# TODO: input output schema definition
|
|
150
|
+
self._infos["status"] = "initialized"
|
|
151
|
+
logger.info("Status initialized!")
|
|
152
|
+
self._infos["config_used"] = self._configuration.model_dump()
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.error(f"Failed to initialize HaystackAgent: {e}")
|
|
155
|
+
raise
|
|
156
|
+
|
|
157
|
+
def _fetch_component_from_module(self) -> Agent | Pipeline:
|
|
158
|
+
"""Fetches the variable that holds the component of an Agent/Pipeline.
|
|
159
|
+
|
|
160
|
+
Returns: Agent | Pipeline.
|
|
161
|
+
"""
|
|
162
|
+
module_path, component_variable_name = _parse_component_definition(
|
|
163
|
+
self._configuration.component_definition
|
|
164
|
+
)
|
|
165
|
+
logger.debug(
|
|
166
|
+
f"Importing spec from file location: {self._configuration.component_definition}"
|
|
167
|
+
)
|
|
168
|
+
try:
|
|
169
|
+
spec = importlib.util.spec_from_file_location(
|
|
170
|
+
component_variable_name, module_path
|
|
171
|
+
)
|
|
172
|
+
if spec is None or spec.loader is None:
|
|
173
|
+
logger.error(f"Could not load spec for module at {module_path}")
|
|
174
|
+
raise ImportError(f"Could not load spec for module at {module_path}")
|
|
175
|
+
|
|
176
|
+
module = importlib.util.module_from_spec(spec)
|
|
177
|
+
logger.debug("Execing module..")
|
|
178
|
+
spec.loader.exec_module(module)
|
|
179
|
+
logger.debug("Module executed")
|
|
180
|
+
|
|
181
|
+
component_variable = getattr(module, component_variable_name)
|
|
182
|
+
logger.info(f"Found component variable: {component_variable}")
|
|
183
|
+
|
|
184
|
+
component = getattr(module, component_variable_name)
|
|
185
|
+
|
|
186
|
+
if not isinstance(component, (Pipeline, Agent)):
|
|
187
|
+
raise TypeError(
|
|
188
|
+
f"The variable '{component_variable_name}' from {module_path} is not a Pipeline or Agent instance. Got {type(component)}"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
return component
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"Invalid component definition string: {self._configuration.component_definition}. Error: {e}"
|
|
196
|
+
) from e
|
|
197
|
+
|
|
198
|
+
def _load_component(self, component_definition: str) -> Agent | Pipeline:
|
|
199
|
+
"""Loads a Haystack component (Agent or Pipeline) from the path (component definition) and returns the agent_instance with langfuse tracing."""
|
|
200
|
+
logger.debug(f"Loading component from: {component_definition}...")
|
|
201
|
+
|
|
202
|
+
component = self._fetch_component_from_module()
|
|
203
|
+
if self._enable_tracing:
|
|
204
|
+
try:
|
|
205
|
+
self._add_langfuse_tracing(component)
|
|
206
|
+
except (FileNotFoundError, ImportError, AttributeError) as e:
|
|
207
|
+
raise ValueError(
|
|
208
|
+
f"Failed to load agent from {component_definition}: {e}"
|
|
209
|
+
) from e
|
|
210
|
+
|
|
211
|
+
return component
|
|
212
|
+
else:
|
|
213
|
+
logger.debug("User wants tracing disabled. Skipping..")
|
|
214
|
+
return component
|
|
215
|
+
|
|
216
|
+
async def invoke(self, message: Any) -> Any:
|
|
217
|
+
"""Process a single input to chat with the agent.The message should be a dictionary containing 'query' and 'session_id'."""
|
|
218
|
+
# TODO: validate actual message
|
|
219
|
+
# TODO: validate input schema
|
|
220
|
+
logger.debug(f"Invoking pipeline for message: {message}")
|
|
221
|
+
if self._agent_instance is None:
|
|
222
|
+
raise RuntimeError(
|
|
223
|
+
"Agent not initialized. Call initialize() before processing messages."
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if (
|
|
227
|
+
not isinstance(message, dict)
|
|
228
|
+
or "query" not in message
|
|
229
|
+
or "session_id" not in message
|
|
230
|
+
):
|
|
231
|
+
raise ValueError(
|
|
232
|
+
"Message must be a dictionary with 'query' and 'session_id' keys."
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
# TODO: support async
|
|
237
|
+
# if pipeline
|
|
238
|
+
if isinstance(self._agent_instance, Pipeline):
|
|
239
|
+
logger.debug("Running Pipeline instance...")
|
|
240
|
+
raw_result = self._agent_instance.run(data={"query": message["query"]})
|
|
241
|
+
result = raw_result["generator"]["replies"][0]
|
|
242
|
+
logger.info(f"Pipeline answer: {result}")
|
|
243
|
+
return result
|
|
244
|
+
|
|
245
|
+
# if agent
|
|
246
|
+
elif isinstance(self._agent_instance, Agent):
|
|
247
|
+
logger.debug("Running Agent instance...")
|
|
248
|
+
raw_result = self._agent_instance.run(
|
|
249
|
+
# TODO: make run method arguments based on component type
|
|
250
|
+
messages=[ChatMessage.from_user(message["query"])]
|
|
251
|
+
) # TODO: from input schema
|
|
252
|
+
logger.info(f"Pipeline answer: {raw_result['messages'][-1].text}")
|
|
253
|
+
result = raw_result["messages"][-1].text
|
|
254
|
+
return result
|
|
255
|
+
|
|
256
|
+
# TODO: validates with output schema, and not hardcodded
|
|
257
|
+
except Exception as e:
|
|
258
|
+
raise RuntimeError(f"Pipeline execution failed: {e}") from e
|
|
259
|
+
|
|
260
|
+
async def stream(self, message: Any) -> Any:
|
|
261
|
+
pass
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Configuration models for Haystack agents."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
5
|
+
from idun_agent_engine.core.engine_config import BaseAgentConfig
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class HaystackAgentConfig(BaseAgentConfig):
|
|
9
|
+
"""Configuration model for Haystack Agents."""
|
|
10
|
+
|
|
11
|
+
type: Literal["haystack"] = "haystack"
|
|
12
|
+
component_type: Literal["pipeline", "agent"]
|
|
13
|
+
component_definition: str
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
def _parse_component_definition(component_definition: str) -> tuple[str, str]:
|
|
2
|
+
try:
|
|
3
|
+
if ":" not in component_definition:
|
|
4
|
+
raise ValueError(
|
|
5
|
+
f" component_definition must be in format: 'path/to/my/module.py:component_variable_name"
|
|
6
|
+
f"got: {component_definition}"
|
|
7
|
+
)
|
|
8
|
+
module_path, component_variable_name = component_definition.rsplit(":", 1)
|
|
9
|
+
return module_path, component_variable_name
|
|
10
|
+
except Exception as e:
|
|
11
|
+
raise ValueError(
|
|
12
|
+
f"Invalid Component Definition format: {component_definition}"
|
|
13
|
+
) from e
|