tongflow 0.2.2__tar.gz → 0.2.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.
Files changed (94) hide show
  1. {tongflow-0.2.2/tongflow.egg-info → tongflow-0.2.4}/PKG-INFO +1 -1
  2. {tongflow-0.2.2 → tongflow-0.2.4}/pyproject.toml +1 -1
  3. tongflow-0.2.4/tests/test_http_store.py +76 -0
  4. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/__init__.py +1 -1
  5. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/__main__.py +4 -1
  6. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/runner.py +15 -4
  7. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/store.py +82 -0
  8. {tongflow-0.2.2 → tongflow-0.2.4/tongflow.egg-info}/PKG-INFO +1 -1
  9. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/SOURCES.txt +1 -0
  10. {tongflow-0.2.2 → tongflow-0.2.4}/LICENSE +0 -0
  11. {tongflow-0.2.2 → tongflow-0.2.4}/README.md +0 -0
  12. {tongflow-0.2.2 → tongflow-0.2.4}/setup.cfg +0 -0
  13. {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_engine.py +0 -0
  14. {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_protocol.py +0 -0
  15. {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_scan.py +0 -0
  16. {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_slots.py +0 -0
  17. {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_subproc.py +0 -0
  18. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/__main__.py +0 -0
  19. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/_ast_utils.py +0 -0
  20. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/_data/tongflow.abi.json +0 -0
  21. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/abi.py +0 -0
  22. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/deploy_marker.py +0 -0
  23. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/__init__.py +0 -0
  24. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/_subproc.py +0 -0
  25. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/abi_schema.py +0 -0
  26. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/assets.py +0 -0
  27. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/bindings.py +0 -0
  28. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/invoker.py +0 -0
  29. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/output_view.py +0 -0
  30. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/paths.py +0 -0
  31. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/plugins.py +0 -0
  32. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/gen_models.py +0 -0
  33. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/gen_node_slots.py +0 -0
  34. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/llm_batch_handlers.py +0 -0
  35. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/__init__.py +0 -0
  36. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/arrange_group.py +0 -0
  37. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/asset.py +0 -0
  38. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_describe.py +0 -0
  39. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_image_gen_video.py +0 -0
  40. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_video_lip_sync.py +0 -0
  41. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/combine_text.py +0 -0
  42. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/concat_videos.py +0 -0
  43. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/convert_voice.py +0 -0
  44. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/denoise_audio.py +0 -0
  45. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/drop_video.py +0 -0
  46. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/extract_audio.py +0 -0
  47. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/gen_music.py +0 -0
  48. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/gen_text.py +0 -0
  49. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/get_first_frame.py +0 -0
  50. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/get_last_frame.py +0 -0
  51. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_describe.py +0 -0
  52. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_edit.py +0 -0
  53. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_fusion.py +0 -0
  54. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen.py +0 -0
  55. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_model.py +0 -0
  56. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_text.py +0 -0
  57. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_video.py +0 -0
  58. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_image_gen_video.py +0 -0
  59. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_upscale.py +0 -0
  60. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/images_gen_video.py +0 -0
  61. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/link.py +0 -0
  62. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/merge_video_audio.py +0 -0
  63. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/parse_document.py +0 -0
  64. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/remove_video_audio.py +0 -0
  65. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/remove_watermark.py +0 -0
  66. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/separate_audio_track.py +0 -0
  67. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/separate_speaker.py +0 -0
  68. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/speech_text_gen_video.py +0 -0
  69. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/speech_video_gen_video.py +0 -0
  70. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/split_text.py +0 -0
  71. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/split_video.py +0 -0
  72. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/subtitle_remove.py +0 -0
  73. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_audio_gen_speech.py +0 -0
  74. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_clone.py +0 -0
  75. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_instruct.py +0 -0
  76. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_preset.py +0 -0
  77. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_video.py +0 -0
  78. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/transcribe.py +0 -0
  79. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/transcribe_timestamp.py +0 -0
  80. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_describe.py +0 -0
  81. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_edit.py +0 -0
  82. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_gen_text.py +0 -0
  83. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_image_gen_video_mix.py +0 -0
  84. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_image_gen_video_move.py +0 -0
  85. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_upscale.py +0 -0
  86. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/node_slots.py +0 -0
  87. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/parse_deploy.py +0 -0
  88. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/progress.py +0 -0
  89. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/protocol.py +0 -0
  90. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/scan.py +0 -0
  91. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/slots.py +0 -0
  92. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/dependency_links.txt +0 -0
  93. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/requires.txt +0 -0
  94. {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tongflow
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for TongFlow — author plugins and run multi-modal GenAI workflows
5
5
  Author: TongFlow contributors
6
6
  License-Expression: AGPL-3.0-only
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tongflow"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Python SDK for TongFlow — author plugins and run multi-modal GenAI workflows"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -0,0 +1,76 @@
1
+ """HttpStore round-trip against a minimal in-process HTTP sink."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import threading
7
+ import urllib.parse
8
+ from http.server import BaseHTTPRequestHandler, HTTPServer
9
+
10
+ from tongflow.engine.store import HttpStore
11
+
12
+ TOKEN = "test-token"
13
+
14
+
15
+ class _Sink(BaseHTTPRequestHandler):
16
+ blobs: dict[str, bytes] = {}
17
+ counter = 0
18
+
19
+ def _authed(self) -> bool:
20
+ return self.headers.get("Authorization") == f"Bearer {TOKEN}"
21
+
22
+ def do_POST(self): # noqa: N802
23
+ if not self._authed():
24
+ self.send_error(401)
25
+ return
26
+ qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
27
+ ext = (qs.get("ext") or ["bin"])[0]
28
+ length = int(self.headers.get("Content-Length", "0"))
29
+ data = self.rfile.read(length)
30
+ _Sink.counter += 1
31
+ file_key = f"tasks/t1/{_Sink.counter}.{ext}"
32
+ _Sink.blobs[file_key] = data
33
+ body = json.dumps({"file_key": file_key}).encode()
34
+ self.send_response(200)
35
+ self.send_header("Content-Type", "application/json")
36
+ self.end_headers()
37
+ self.wfile.write(body)
38
+
39
+ def do_GET(self): # noqa: N802
40
+ if not self._authed():
41
+ self.send_error(401)
42
+ return
43
+ qs = urllib.parse.parse_qs(urllib.parse.urlparse(self.path).query)
44
+ key = (qs.get("file_key") or [""])[0]
45
+ blob = _Sink.blobs.get(key)
46
+ if blob is None:
47
+ self.send_error(404)
48
+ return
49
+ self.send_response(200)
50
+ self.send_header("Content-Type", "application/octet-stream")
51
+ self.end_headers()
52
+ self.wfile.write(blob)
53
+
54
+ def log_message(self, *args): # silence
55
+ pass
56
+
57
+
58
+ def test_http_store_roundtrip():
59
+ server = HTTPServer(("127.0.0.1", 0), _Sink)
60
+ thread = threading.Thread(target=server.serve_forever, daemon=True)
61
+ thread.start()
62
+ try:
63
+ endpoint = f"http://127.0.0.1:{server.server_address[1]}/assets"
64
+ store = HttpStore(endpoint, TOKEN)
65
+
66
+ ref = store.put(b"hello-bytes", mime="image/png", ext="png")
67
+ assert ref["file_key"].endswith(".png")
68
+ assert ref["mime"] == "image/png"
69
+
70
+ assert store.get(ref["file_key"]) == b"hello-bytes"
71
+ assert store.get("tasks/does-not-exist.png") is None
72
+ # URL-ish refs skip the sink entirely
73
+ assert store.get("https://example.com/x.png") is None
74
+ assert store.get("mem://abc") is None
75
+ finally:
76
+ server.shutdown()
@@ -11,6 +11,6 @@ from .deploy_marker import deploy
11
11
  from .engine import run_workflow
12
12
  from .progress import progress
13
13
 
14
- __version__ = "0.2.2"
14
+ __version__ = "0.2.4"
15
15
 
16
16
  __all__ = ["deploy", "progress", "run_workflow"]
@@ -9,7 +9,8 @@ Request shape::
9
9
  {"workflow": <executable workflow dict>,
10
10
  "inputs": {<name>: ...},
11
11
  "options": {"plugins_dir", "data_dir", "out_dir", "abi_path",
12
- "file_key_base", "auto_install", "org", "task_id"}}
12
+ "file_key_base", "inline_outputs", "asset_endpoint",
13
+ "asset_token", "auto_install", "org", "task_id"}}
13
14
 
14
15
  The TongFlow desktop app uses this to delegate workflow execution to the SDK
15
16
  engine (one execution core) while keeping its own DB / SSE / abort shell. The
@@ -59,6 +60,8 @@ def main() -> int:
59
60
  abi_path=opts.get("abi_path"),
60
61
  file_key_base=opts.get("file_key_base"),
61
62
  inline_outputs=bool(opts.get("inline_outputs", True)),
63
+ asset_endpoint=opts.get("asset_endpoint"),
64
+ asset_token=opts.get("asset_token"),
62
65
  auto_install=bool(opts.get("auto_install", True)),
63
66
  org=opts.get("org") or "https://github.com/tong-io",
64
67
  plugin_git_urls=opts.get("plugin_git_urls"),
@@ -37,7 +37,7 @@ from .plugins import (
37
37
  prepare_python_env,
38
38
  scan_manifest,
39
39
  )
40
- from .store import AssetStore, DiskStore, MemoryStore
40
+ from .store import AssetStore, DiskStore, HttpStore, MemoryStore
41
41
 
42
42
  EventCb = Callable[[dict[str, Any]], None]
43
43
 
@@ -166,6 +166,8 @@ def run_workflow(
166
166
  abi_path: Optional[Union[str, Path]] = None,
167
167
  file_key_base: Optional[Union[str, Path]] = None,
168
168
  inline_outputs: bool = True,
169
+ asset_endpoint: Optional[str] = None,
170
+ asset_token: Optional[str] = None,
169
171
  auto_install: bool = True,
170
172
  org: str = DEFAULT_ORG,
171
173
  plugin_git_urls: Optional[dict[str, str]] = None,
@@ -191,6 +193,11 @@ def run_workflow(
191
193
  returned as ``{bytesBase64, mime?, filename?}`` — zero files written
192
194
  (besides the required plugin clone / venv). When False, outputs are
193
195
  written to ``out_dir`` and returned as ``file_key`` paths.
196
+ asset_endpoint / asset_token: host-managed asset store (loopback HTTP
197
+ sink, see :class:`~tongflow.engine.store.HttpStore`). When set it
198
+ overrides ``inline_outputs``: outputs are POSTed to the host and
199
+ input ``file_key`` refs are fetched from it — the engine touches
200
+ no asset files on disk.
194
201
  auto_install: clone missing plugins and provision a shared venv.
195
202
  org / plugin_git_urls: where to clone official / custom plugins from.
196
203
  on_progress: optional callback receiving progress event dicts.
@@ -212,9 +219,13 @@ def run_workflow(
212
219
  # Output store: in-memory (inline, zero disk) or on-disk (file_key paths,
213
220
  # used by the desktop delegation so the canvas reads via /api/uploads).
214
221
  out_path = Path(out_dir).resolve() if out_dir else (data_dir / "engine-out")
215
- store: AssetStore = (
216
- MemoryStore() if inline_outputs else DiskStore(out_path, fk_base)
217
- )
222
+ store: AssetStore
223
+ if asset_endpoint:
224
+ store = HttpStore(asset_endpoint, asset_token)
225
+ elif inline_outputs:
226
+ store = MemoryStore()
227
+ else:
228
+ store = DiskStore(out_path, fk_base)
218
229
 
219
230
  def emit(event: dict[str, Any]) -> None:
220
231
  if on_progress is not None:
@@ -7,6 +7,11 @@
7
7
  are written to ``out_dir`` and the ``file_key`` is a path (relative to
8
8
  ``file_key_base`` when set, else absolute), matching the server's
9
9
  ``saveFile`` contract so the canvas can read them via ``/api/uploads``.
10
+ - :class:`HttpStore` (``asset_endpoint`` option): the embedding host owns
11
+ file storage (e.g. a cloud backend writing to object storage). ``put``
12
+ POSTs raw bytes over loopback HTTP and gets back the host-assigned
13
+ ``file_key``; ``get`` fetches bytes for any ``file_key`` the host knows.
14
+ The engine never sees storage credentials.
10
15
 
11
16
  ``get`` returns the bytes for a key the store owns, else ``None`` (so the
12
17
  filesystem/URL resolver in :mod:`assets` handles plain file_keys / URLs).
@@ -14,7 +19,11 @@ filesystem/URL resolver in :mod:`assets` handles plain file_keys / URLs).
14
19
 
15
20
  from __future__ import annotations
16
21
 
22
+ import json
17
23
  import os
24
+ import urllib.error
25
+ import urllib.parse
26
+ import urllib.request
18
27
  import uuid
19
28
  from pathlib import Path
20
29
  from typing import Optional, Protocol
@@ -94,3 +103,76 @@ class DiskStore:
94
103
  def get(self, file_key: str) -> Optional[bytes]:
95
104
  # Disk file_keys are resolved by the filesystem/URL resolver in assets.
96
105
  return None
106
+
107
+
108
+ class HttpStore:
109
+ """Host-managed asset store over (loopback) HTTP.
110
+
111
+ The host exposes a single endpoint:
112
+
113
+ - ``POST <endpoint>?ext=..&mime=..&filename=..`` with raw bytes as the
114
+ body -> ``{"file_key": "..."}``
115
+ - ``GET <endpoint>?file_key=..`` -> raw bytes (404 when unknown)
116
+
117
+ Authentication is a Bearer token minted by the host per engine run.
118
+ """
119
+
120
+ # Refs the host sink cannot serve; skip the round trip and let the
121
+ # URL/data resolver in assets handle them.
122
+ _SKIP_PREFIXES = ("http://", "https://", "data:", "mem://")
123
+
124
+ def __init__(self, endpoint: str, token: Optional[str] = None) -> None:
125
+ self.endpoint = endpoint
126
+ self.token = token
127
+
128
+ def _headers(self) -> dict[str, str]:
129
+ return {"Authorization": f"Bearer {self.token}"} if self.token else {}
130
+
131
+ def put(
132
+ self,
133
+ data: bytes,
134
+ *,
135
+ mime: Optional[str] = None,
136
+ filename: Optional[str] = None,
137
+ ext: str = "bin",
138
+ ) -> dict[str, str]:
139
+ params = {"ext": ext.lstrip(".") or "bin"}
140
+ if mime:
141
+ params["mime"] = mime
142
+ if filename:
143
+ params["filename"] = filename
144
+ req = urllib.request.Request(
145
+ f"{self.endpoint}?{urllib.parse.urlencode(params)}",
146
+ data=bytes(data),
147
+ method="POST",
148
+ headers={
149
+ "Content-Type": "application/octet-stream",
150
+ **self._headers(),
151
+ },
152
+ )
153
+ with urllib.request.urlopen(req) as resp:
154
+ payload = json.loads(resp.read().decode("utf-8"))
155
+ file_key = payload.get("file_key")
156
+ if not isinstance(file_key, str) or not file_key:
157
+ raise RuntimeError("asset endpoint returned no file_key")
158
+ out: dict[str, str] = {"file_key": file_key}
159
+ if mime:
160
+ out["mime"] = mime
161
+ if filename:
162
+ out["filename"] = filename
163
+ return out
164
+
165
+ def get(self, file_key: str) -> Optional[bytes]:
166
+ if file_key.startswith(self._SKIP_PREFIXES):
167
+ return None
168
+ req = urllib.request.Request(
169
+ f"{self.endpoint}?{urllib.parse.urlencode({'file_key': file_key})}",
170
+ headers=self._headers(),
171
+ )
172
+ try:
173
+ with urllib.request.urlopen(req) as resp:
174
+ return resp.read()
175
+ except urllib.error.HTTPError as e:
176
+ if e.code == 404:
177
+ return None
178
+ raise
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tongflow
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for TongFlow — author plugins and run multi-modal GenAI workflows
5
5
  Author: TongFlow contributors
6
6
  License-Expression: AGPL-3.0-only
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  tests/test_engine.py
5
+ tests/test_http_store.py
5
6
  tests/test_protocol.py
6
7
  tests/test_scan.py
7
8
  tests/test_slots.py
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