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.
- {tongflow-0.2.2/tongflow.egg-info → tongflow-0.2.4}/PKG-INFO +1 -1
- {tongflow-0.2.2 → tongflow-0.2.4}/pyproject.toml +1 -1
- tongflow-0.2.4/tests/test_http_store.py +76 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/__init__.py +1 -1
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/__main__.py +4 -1
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/runner.py +15 -4
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/store.py +82 -0
- {tongflow-0.2.2 → tongflow-0.2.4/tongflow.egg-info}/PKG-INFO +1 -1
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/SOURCES.txt +1 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/LICENSE +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/README.md +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/setup.cfg +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_engine.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_protocol.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_scan.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_slots.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tests/test_subproc.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/__main__.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/_ast_utils.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/_data/tongflow.abi.json +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/abi.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/deploy_marker.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/__init__.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/_subproc.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/abi_schema.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/assets.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/bindings.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/invoker.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/output_view.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/paths.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/engine/plugins.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/gen_models.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/gen_node_slots.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/llm_batch_handlers.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/__init__.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/arrange_group.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/asset.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_describe.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_image_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/audio_video_lip_sync.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/combine_text.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/concat_videos.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/convert_voice.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/denoise_audio.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/drop_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/extract_audio.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/gen_music.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/gen_text.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/get_first_frame.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/get_last_frame.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_describe.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_edit.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_fusion.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_model.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_text.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_image_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/image_upscale.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/images_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/link.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/merge_video_audio.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/parse_document.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/remove_video_audio.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/remove_watermark.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/separate_audio_track.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/separate_speaker.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/speech_text_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/speech_video_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/split_text.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/split_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/subtitle_remove.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_audio_gen_speech.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_clone.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_instruct.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_speech_preset.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/text_gen_video.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/transcribe.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/transcribe_timestamp.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_describe.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_edit.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_gen_text.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_image_gen_video_mix.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_image_gen_video_move.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/models/video_upscale.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/node_slots.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/parse_deploy.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/progress.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/protocol.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/scan.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow/slots.py +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/dependency_links.txt +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/requires.txt +0 -0
- {tongflow-0.2.2 → tongflow-0.2.4}/tongflow.egg-info/top_level.txt +0 -0
|
@@ -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()
|
|
@@ -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", "
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|