model-compose 0.1.3__tar.gz → 0.1.4__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.
- {model_compose-0.1.3/src/model_compose.egg-info → model_compose-0.1.4}/PKG-INFO +1 -1
- {model_compose-0.1.3 → model_compose-0.1.4}/pyproject.toml +1 -1
- model_compose-0.1.4/src/mindor/core/controller/runner/http_client.py +68 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/webui/gradio.py +37 -8
- model_compose-0.1.4/src/mindor/core/utils/http_client.py +97 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/http_request.py +21 -4
- {model_compose-0.1.3 → model_compose-0.1.4/src/model_compose.egg-info}/PKG-INFO +1 -1
- model_compose-0.1.3/src/mindor/core/controller/runner/http_client.py +0 -26
- model_compose-0.1.3/src/mindor/core/utils/http_client.py +0 -73
- {model_compose-0.1.3 → model_compose-0.1.4}/LICENSE +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/README.md +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/setup.cfg +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/cli/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/cli/compose.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/component.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/engine/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/engine/base.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/engine/context.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/engine/http_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/component/engine/http_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/compose/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/compose/compose.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/compose/manager.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/controller.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/engine/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/engine/base.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/engine/http_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/engine/mcp_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/runner/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/runner/client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/runner/mcp_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/runner/runner.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/webui/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/webui/schema.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/webui/webui.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/gateway/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/gateway/engine/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/gateway/engine/base.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/gateway/engine/http_tunnel.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/gateway/gateway.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/engine/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/engine/base.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/engine/http_callback.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/listener.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/runtime/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/runtime/env.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/services/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/services/async_service.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/expiring.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/image.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/streaming.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/template.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/time.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/utils/workqueue.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/workflow/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/workflow/context.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/workflow/job.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/workflow/workflow.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/loader.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/action.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/http_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/http_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/mcp_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/mcp_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/component.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/http_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/http_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/mcp_client.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/mcp_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/types.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/compose.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/controller.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/http_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/mcp_server.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/types.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/webui.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/gateway.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/impl/types.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/impl/http_callback.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/impl/types.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/listener.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/impl/__init__.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/impl/common.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/impl/docker.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/impl/types.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/runtime/runtime.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/workflow.py +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/model_compose.egg-info/SOURCES.txt +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/model_compose.egg-info/dependency_links.txt +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/model_compose.egg-info/entry_points.txt +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/model_compose.egg-info/requires.txt +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/src/model_compose.egg-info/top_level.txt +0 -0
- {model_compose-0.1.3 → model_compose-0.1.4}/tests/test_cli.py +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
+
from mindor.dsl.schema.controller import ControllerConfig
|
|
3
|
+
from mindor.core.utils.http_client import HttpClient
|
|
4
|
+
from .runner import ControllerClient
|
|
5
|
+
from starlette.datastructures import UploadFile
|
|
6
|
+
|
|
7
|
+
class HttpControllerClient(ControllerClient):
|
|
8
|
+
def __init__(self, config: ControllerConfig):
|
|
9
|
+
super().__init__(config)
|
|
10
|
+
|
|
11
|
+
self.client: HttpClient = HttpClient()
|
|
12
|
+
|
|
13
|
+
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
14
|
+
base_path = self.config.base_path if self.config.base_path else ""
|
|
15
|
+
url = f"http://localhost:{self.config.port}{base_path}/workflows"
|
|
16
|
+
method = "POST"
|
|
17
|
+
body = {
|
|
18
|
+
"workflow_id": workflow_id,
|
|
19
|
+
"input": input,
|
|
20
|
+
"wait_for_completion": True,
|
|
21
|
+
"output_only": True
|
|
22
|
+
}
|
|
23
|
+
headers = {
|
|
24
|
+
"Content-Type": "application/json"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if self._contains_upload_file(input):
|
|
28
|
+
body = { key: value for key, value in self._flatten_for_multipart(body) }
|
|
29
|
+
headers = {
|
|
30
|
+
"Content-Type": "multipart/form-data"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return await self.client.request(url, method, None, body, headers)
|
|
34
|
+
|
|
35
|
+
def _flatten_for_multipart(self, body: Dict[str, Any], key: str = "") -> List[Tuple[str, Any]]:
|
|
36
|
+
flattened = []
|
|
37
|
+
|
|
38
|
+
for subkey, value in body.items():
|
|
39
|
+
full_key = f"{key}[{subkey}]" if key else subkey
|
|
40
|
+
|
|
41
|
+
if isinstance(value, dict):
|
|
42
|
+
flattened.extend(self._flatten_for_multipart(value, full_key))
|
|
43
|
+
continue
|
|
44
|
+
|
|
45
|
+
if isinstance(value, list):
|
|
46
|
+
for item in value:
|
|
47
|
+
list_key = f"{full_key}[]"
|
|
48
|
+
if isinstance(item, (dict, list)):
|
|
49
|
+
flattened.extend(self._flatten_for_multipart({ list_key: item }))
|
|
50
|
+
else:
|
|
51
|
+
flattened.append((list_key, item))
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
flattened.append((full_key, value))
|
|
55
|
+
|
|
56
|
+
return flattened
|
|
57
|
+
|
|
58
|
+
def _contains_upload_file(self, value: Any) -> bool:
|
|
59
|
+
if isinstance(value, UploadFile):
|
|
60
|
+
return True
|
|
61
|
+
|
|
62
|
+
if isinstance(value, dict):
|
|
63
|
+
return any(self._contains_upload_file(v) for v in value.values())
|
|
64
|
+
|
|
65
|
+
if isinstance(value, list):
|
|
66
|
+
return any(self._contains_upload_file(v) for v in value)
|
|
67
|
+
|
|
68
|
+
return False
|
|
@@ -4,8 +4,9 @@ from mindor.core.utils.http_client import HttpClient, HttpStreamResource
|
|
|
4
4
|
from mindor.core.utils.image import load_image_from_stream
|
|
5
5
|
from mindor.core.utils.streaming import Base64StreamResource, save_stream_to_temporary_file
|
|
6
6
|
from .schema import WorkflowSchema
|
|
7
|
+
from starlette.datastructures import UploadFile
|
|
7
8
|
import gradio as gr
|
|
8
|
-
import json
|
|
9
|
+
import json, os
|
|
9
10
|
|
|
10
11
|
class ComponentGroup:
|
|
11
12
|
def __init__(self, group: gr.Component, components: List[gr.Component]):
|
|
@@ -45,9 +46,9 @@ class GradioWebUIBuilder:
|
|
|
45
46
|
output_components = gr.Textbox(label="", lines=8, interactive=False, show_copy_button=True)
|
|
46
47
|
|
|
47
48
|
async def _run_workflow(*args):
|
|
48
|
-
input =
|
|
49
|
+
input = self._build_input_values(args, workflow.input)
|
|
49
50
|
output = await runner(input)
|
|
50
|
-
output = await self.
|
|
51
|
+
output = await self._flatten_output_values(output, workflow.output)
|
|
51
52
|
return output[0] if len(output) == 1 else output
|
|
52
53
|
|
|
53
54
|
run_button.click(
|
|
@@ -78,8 +79,11 @@ class GradioWebUIBuilder:
|
|
|
78
79
|
if variable.type == "image":
|
|
79
80
|
return gr.Image(label=label, type="filepath")
|
|
80
81
|
|
|
82
|
+
if variable.type == "audio":
|
|
83
|
+
return gr.Audio(label=label, type="filepath")
|
|
84
|
+
|
|
81
85
|
if variable.type == "video":
|
|
82
|
-
return gr.Video(label=label)
|
|
86
|
+
return gr.Video(label=label, type="filepath")
|
|
83
87
|
|
|
84
88
|
if variable.type == "file":
|
|
85
89
|
return gr.File(label=label)
|
|
@@ -88,6 +92,31 @@ class GradioWebUIBuilder:
|
|
|
88
92
|
return gr.Dropdown(choices=variable.options or [], label=label, value=default, info=info)
|
|
89
93
|
|
|
90
94
|
return gr.Textbox(label=label, value=default, info=f"Unsupported type: {variable.type}")
|
|
95
|
+
|
|
96
|
+
def _build_input_values(self, arguments: List[Any], variables: List[WorkflowVariableConfig]) -> Any:
|
|
97
|
+
if len(variables) == 1 and not variables[0].name:
|
|
98
|
+
return arguments[0]
|
|
99
|
+
|
|
100
|
+
input: Dict[str, Any] = {}
|
|
101
|
+
|
|
102
|
+
for value, variable in zip(arguments, variables):
|
|
103
|
+
input[variable.name] = self._convert_input_value(value, variable)
|
|
104
|
+
|
|
105
|
+
return input
|
|
106
|
+
|
|
107
|
+
def _convert_input_value(self, value: Any, variable: WorkflowVariableConfig) -> Any:
|
|
108
|
+
if variable.type in [ "image", "audio", "video", "file" ]:
|
|
109
|
+
file, filename = open(value, "rb"), os.path.basename(value)
|
|
110
|
+
headers = {
|
|
111
|
+
"Content-Type": self._guess_file_content_type(filename, variable),
|
|
112
|
+
"Content-Disposition": f'form-data; filename="{filename}"'
|
|
113
|
+
}
|
|
114
|
+
return UploadFile(file=file, filename=filename, headers=headers)
|
|
115
|
+
|
|
116
|
+
return value if value != "" else None
|
|
117
|
+
|
|
118
|
+
def _guess_file_content_type(self, filename: str, variable: WorkflowVariableConfig) -> str:
|
|
119
|
+
return "application/octet-stream"
|
|
91
120
|
|
|
92
121
|
def _build_output_component(self, variable: Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]) -> Union[gr.Component, List[ComponentGroup]]:
|
|
93
122
|
if isinstance(variable, WorkflowVariableGroupConfig):
|
|
@@ -104,7 +133,7 @@ class GradioWebUIBuilder:
|
|
|
104
133
|
|
|
105
134
|
if variable.type == "string":
|
|
106
135
|
return gr.Textbox(label=label, interactive=False, show_copy_button=True, info=info)
|
|
107
|
-
|
|
136
|
+
|
|
108
137
|
if variable.type == "image":
|
|
109
138
|
return gr.Image(label=label, interactive=False)
|
|
110
139
|
|
|
@@ -126,13 +155,13 @@ class GradioWebUIBuilder:
|
|
|
126
155
|
flattened.append(item)
|
|
127
156
|
return flattened
|
|
128
157
|
|
|
129
|
-
async def
|
|
158
|
+
async def _flatten_output_values(self, output: Any, variables: List[Union[WorkflowVariableConfig, WorkflowVariableGroupConfig]]) -> Any:
|
|
130
159
|
flattened = []
|
|
131
160
|
for variable in variables:
|
|
132
161
|
if isinstance(variable, WorkflowVariableGroupConfig):
|
|
133
162
|
group = output[variable.name] if variable.name else output
|
|
134
163
|
for value in group:
|
|
135
|
-
flattened.extend(await self.
|
|
164
|
+
flattened.extend(await self._flatten_output_values(value, variable.variables))
|
|
136
165
|
else:
|
|
137
166
|
value = output[variable.name] if variable.name else output
|
|
138
167
|
flattened.append(await self._convert_type(value, variable.type, variable.subtype, variable.format))
|
|
@@ -143,7 +172,7 @@ class GradioWebUIBuilder:
|
|
|
143
172
|
if isinstance(value, (dict, list)):
|
|
144
173
|
return json.dumps(value)
|
|
145
174
|
return str(value)
|
|
146
|
-
|
|
175
|
+
|
|
147
176
|
if type == "image":
|
|
148
177
|
if format == "url" and isinstance(value, str):
|
|
149
178
|
return await load_image_from_stream(await HttpClient().request(value), subtype)
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, AsyncIterator, Any
|
|
2
|
+
from .http_request import build_request_body, parse_options_header
|
|
3
|
+
from .streaming import StreamResource
|
|
4
|
+
from requests.structures import CaseInsensitiveDict
|
|
5
|
+
import aiohttp
|
|
6
|
+
|
|
7
|
+
class HttpStreamResource(StreamResource):
|
|
8
|
+
def __init__(
|
|
9
|
+
self,
|
|
10
|
+
session: aiohttp.ClientSession,
|
|
11
|
+
stream: aiohttp.StreamReader,
|
|
12
|
+
content_type: Optional[str] = None,
|
|
13
|
+
filename: Optional[str] = None
|
|
14
|
+
):
|
|
15
|
+
super().__init__(content_type, filename)
|
|
16
|
+
|
|
17
|
+
self.session: aiohttp.ClientSession = session
|
|
18
|
+
self.stream: aiohttp.StreamReader = stream
|
|
19
|
+
|
|
20
|
+
async def close(self):
|
|
21
|
+
await self.session.close()
|
|
22
|
+
self.session = None
|
|
23
|
+
self.stream = None
|
|
24
|
+
|
|
25
|
+
async def _iterate_stream(self) -> AsyncIterator[bytes]:
|
|
26
|
+
_, buffer_size = self.stream.get_read_buffer_limits()
|
|
27
|
+
chunk_size = buffer_size or 65536
|
|
28
|
+
|
|
29
|
+
while not self.stream.at_eof():
|
|
30
|
+
chunk = await self.stream.read(chunk_size)
|
|
31
|
+
if not chunk:
|
|
32
|
+
break
|
|
33
|
+
yield chunk
|
|
34
|
+
|
|
35
|
+
class HttpClient:
|
|
36
|
+
async def request(
|
|
37
|
+
self,
|
|
38
|
+
url: str,
|
|
39
|
+
method: Optional[str] = "GET",
|
|
40
|
+
params: Optional[Dict[str, Any]] = None,
|
|
41
|
+
body: Optional[Any] = None,
|
|
42
|
+
headers: Optional[Dict[str, str]] = None
|
|
43
|
+
) -> Any:
|
|
44
|
+
session = aiohttp.ClientSession()
|
|
45
|
+
try:
|
|
46
|
+
response = await self._request_with_session(session, url, method, params, body, headers)
|
|
47
|
+
content, _ = await self._parse_response_content(session, response)
|
|
48
|
+
|
|
49
|
+
if response.status >= 400:
|
|
50
|
+
raise ValueError(f"Request failed with status {response.status}: {content}")
|
|
51
|
+
|
|
52
|
+
if not isinstance(content, HttpStreamResource):
|
|
53
|
+
await session.close()
|
|
54
|
+
|
|
55
|
+
return content
|
|
56
|
+
except:
|
|
57
|
+
await session.close()
|
|
58
|
+
raise
|
|
59
|
+
|
|
60
|
+
async def _request_with_session(
|
|
61
|
+
self,
|
|
62
|
+
session: aiohttp.ClientSession,
|
|
63
|
+
url: str,
|
|
64
|
+
method: str,
|
|
65
|
+
params: Optional[Dict[str, Any]],
|
|
66
|
+
body: Optional[Any],
|
|
67
|
+
headers: Optional[Dict[str, str]]
|
|
68
|
+
) -> aiohttp.ClientResponse:
|
|
69
|
+
data, content_type = self._build_request_body(body, headers)
|
|
70
|
+
|
|
71
|
+
if content_type == "multipart/form-data":
|
|
72
|
+
headers = CaseInsensitiveDict(headers)
|
|
73
|
+
headers.pop("Content-Type", None)
|
|
74
|
+
|
|
75
|
+
return await session.request(method, url, params=params, data=data, headers=headers)
|
|
76
|
+
|
|
77
|
+
def _build_request_body(self, body: Optional[Any], headers: Optional[Dict[str, str]]) -> Tuple[Any, str]:
|
|
78
|
+
content_type, _ = parse_options_header(headers, "Content-Type")
|
|
79
|
+
|
|
80
|
+
if content_type and body is not None:
|
|
81
|
+
return (build_request_body(body, content_type), content_type)
|
|
82
|
+
|
|
83
|
+
return (body, content_type)
|
|
84
|
+
|
|
85
|
+
async def _parse_response_content(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse) -> Tuple[Any, str]:
|
|
86
|
+
content_type, _ = parse_options_header(response.headers, "Content-Type")
|
|
87
|
+
|
|
88
|
+
if content_type == "application/json":
|
|
89
|
+
return (await response.json(), content_type)
|
|
90
|
+
|
|
91
|
+
if content_type.startswith("text/"):
|
|
92
|
+
return (await response.text(), content_type)
|
|
93
|
+
|
|
94
|
+
_, disposition = parse_options_header(response.headers, "Content-Disposition")
|
|
95
|
+
filename = disposition.get("filename")
|
|
96
|
+
|
|
97
|
+
return (HttpStreamResource(session, response.content, content_type, filename), content_type)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
from typing import Dict, List, Tuple, Optional, Any
|
|
2
2
|
from requests.structures import CaseInsensitiveDict
|
|
3
|
-
from starlette.datastructures import FormData
|
|
3
|
+
from starlette.datastructures import FormData, UploadFile
|
|
4
4
|
from urllib.parse import urlencode
|
|
5
5
|
from fastapi import Request
|
|
6
|
-
import re, json
|
|
6
|
+
import aiohttp, re, json
|
|
7
7
|
|
|
8
8
|
_token_pattern = r"([\w!#$%&'*+\-.^_`|~]+)"
|
|
9
9
|
_quoted_pattern = r'"([^"]*)"'
|
|
@@ -37,12 +37,12 @@ class NestedFormParser:
|
|
|
37
37
|
|
|
38
38
|
return parts
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
def build_request_body(body: Any, content_type: Optional[str]) -> Any:
|
|
41
41
|
if content_type == "application/json":
|
|
42
42
|
return json.dumps(body)
|
|
43
43
|
|
|
44
44
|
if content_type == "multipart/form-data":
|
|
45
|
-
return
|
|
45
|
+
return build_request_form(body)
|
|
46
46
|
|
|
47
47
|
if content_type == "application/x-www-form-urlencoded":
|
|
48
48
|
return urlencode(body)
|
|
@@ -52,6 +52,22 @@ async def serialize_request_body(body: Any, content_type: Optional[str]) -> Any:
|
|
|
52
52
|
|
|
53
53
|
return body
|
|
54
54
|
|
|
55
|
+
def build_request_form(body: Any) -> aiohttp.FormData:
|
|
56
|
+
form = aiohttp.FormData()
|
|
57
|
+
|
|
58
|
+
for key, value in body.items() if isinstance(body, dict) else {}:
|
|
59
|
+
if isinstance(value, UploadFile):
|
|
60
|
+
form.add_field(
|
|
61
|
+
name=key,
|
|
62
|
+
value=value.file,
|
|
63
|
+
filename=value.filename,
|
|
64
|
+
content_type=value.content_type or "application/octet-stream"
|
|
65
|
+
)
|
|
66
|
+
else:
|
|
67
|
+
form.add_field(name=key, value=str(value))
|
|
68
|
+
|
|
69
|
+
return form
|
|
70
|
+
|
|
55
71
|
async def parse_request_body(request: Request, content_type: str, nested: bool = False) -> Any:
|
|
56
72
|
if content_type in [ "multipart/form-data", "application/x-www-form-urlencoded" ]:
|
|
57
73
|
return await parse_request_form(request, nested)
|
|
@@ -75,6 +91,7 @@ async def parse_request_form(request: Request, nested: bool = False) -> Any:
|
|
|
75
91
|
def parse_options_header(headers: Dict[str, str], header_name: str) -> Tuple[str, Dict[str, str]]:
|
|
76
92
|
value = CaseInsensitiveDict(headers or {}).get(header_name, "")
|
|
77
93
|
pos = _firefox_escape_fix_regex.sub("%22", value).find(";")
|
|
94
|
+
|
|
78
95
|
if pos >= 0:
|
|
79
96
|
options = {
|
|
80
97
|
m.group(1).lower(): m.group(2) or m.group(3).replace("%22", "") for m in _option_regex.finditer(value[pos:])
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, Any
|
|
2
|
-
from mindor.dsl.schema.controller import ControllerConfig
|
|
3
|
-
from mindor.core.utils.http_client import HttpClient
|
|
4
|
-
from .runner import ControllerClient
|
|
5
|
-
|
|
6
|
-
class HttpControllerClient(ControllerClient):
|
|
7
|
-
def __init__(self, config: ControllerConfig):
|
|
8
|
-
super().__init__(config)
|
|
9
|
-
|
|
10
|
-
self.client: HttpClient = HttpClient()
|
|
11
|
-
|
|
12
|
-
async def run_workflow(self, workflow_id: Optional[str], input: Any) -> Any:
|
|
13
|
-
base_path = self.config.base_path if self.config.base_path else ""
|
|
14
|
-
url = f"http://localhost:{self.config.port}{base_path}/workflows"
|
|
15
|
-
method = "POST"
|
|
16
|
-
body = {
|
|
17
|
-
"workflow_id": workflow_id,
|
|
18
|
-
"input": input,
|
|
19
|
-
"wait_for_completion": True,
|
|
20
|
-
"output_only": True
|
|
21
|
-
}
|
|
22
|
-
headers = {
|
|
23
|
-
"Content-Type": "application/json"
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return await self.client.request(url, method, None, body, headers)
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
from typing import Type, Union, Literal, Optional, Dict, List, Tuple, Set, Annotated, AsyncIterator, Any
|
|
2
|
-
from .http_request import serialize_request_body, parse_options_header
|
|
3
|
-
from .streaming import StreamResource
|
|
4
|
-
import aiohttp
|
|
5
|
-
|
|
6
|
-
class HttpStreamResource(StreamResource):
|
|
7
|
-
def __init__(self, session: aiohttp.ClientSession, stream: aiohttp.StreamReader, content_type: Optional[str] = None, filename: Optional[str] = None):
|
|
8
|
-
super().__init__(content_type, filename)
|
|
9
|
-
|
|
10
|
-
self.session: aiohttp.ClientSession = session
|
|
11
|
-
self.stream: aiohttp.StreamReader = stream
|
|
12
|
-
|
|
13
|
-
async def close(self):
|
|
14
|
-
await self.session.close()
|
|
15
|
-
self.session = None
|
|
16
|
-
self.stream = None
|
|
17
|
-
|
|
18
|
-
async def _iterate_stream(self) -> AsyncIterator[bytes]:
|
|
19
|
-
_, buffer_size = self.stream.get_read_buffer_limits()
|
|
20
|
-
chunk_size = buffer_size or 65536
|
|
21
|
-
|
|
22
|
-
while not self.stream.at_eof():
|
|
23
|
-
chunk = await self.stream.read(chunk_size)
|
|
24
|
-
if not chunk:
|
|
25
|
-
break
|
|
26
|
-
yield chunk
|
|
27
|
-
|
|
28
|
-
class HttpClient:
|
|
29
|
-
async def request(self, url: str, method: Optional[str] = "GET", params: Optional[Dict[str, Any]] = None, body: Optional[Any] = None, headers: Optional[Dict[str, str]] = None) -> Any:
|
|
30
|
-
session = aiohttp.ClientSession()
|
|
31
|
-
try:
|
|
32
|
-
response = await session.request(
|
|
33
|
-
method,
|
|
34
|
-
url,
|
|
35
|
-
params=params,
|
|
36
|
-
data=await self._serialize_request_body(body, headers),
|
|
37
|
-
headers=headers
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
content = await self._parse_response_content(session, response)
|
|
41
|
-
|
|
42
|
-
if response.status >= 400:
|
|
43
|
-
raise ValueError(f"Request failed with status {response.status}: {content}")
|
|
44
|
-
|
|
45
|
-
if not isinstance(content, HttpStreamResource):
|
|
46
|
-
await session.close()
|
|
47
|
-
|
|
48
|
-
return content
|
|
49
|
-
except:
|
|
50
|
-
await session.close()
|
|
51
|
-
raise
|
|
52
|
-
|
|
53
|
-
async def _serialize_request_body(self, body: Optional[Any], headers: Optional[Dict[str, str]]) -> Any:
|
|
54
|
-
content_type, _ = parse_options_header(headers, "Content-Type")
|
|
55
|
-
|
|
56
|
-
if content_type and body:
|
|
57
|
-
return await serialize_request_body(body, content_type)
|
|
58
|
-
|
|
59
|
-
return body
|
|
60
|
-
|
|
61
|
-
async def _parse_response_content(self, session: aiohttp.ClientSession, response: aiohttp.ClientResponse) -> Any:
|
|
62
|
-
content_type, _ = parse_options_header(response.headers, "Content-Type")
|
|
63
|
-
|
|
64
|
-
if content_type == "application/json":
|
|
65
|
-
return await response.json()
|
|
66
|
-
|
|
67
|
-
if content_type.startswith("text/"):
|
|
68
|
-
return await response.text()
|
|
69
|
-
|
|
70
|
-
_, disposition = parse_options_header(response.headers, "Content-Disposition")
|
|
71
|
-
filename = disposition.get("filename")
|
|
72
|
-
|
|
73
|
-
return HttpStreamResource(session, response.content, content_type, filename)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/controller/engine/http_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/core/listener/engine/http_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/http_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/action/impl/http_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/http_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/http_server.py
RENAMED
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/mcp_client.py
RENAMED
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/component/impl/mcp_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/http_server.py
RENAMED
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/controller/impl/mcp_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/gateway/impl/http_tunnel.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{model_compose-0.1.3 → model_compose-0.1.4}/src/mindor/dsl/schema/listener/impl/http_callback.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|