pipelex-api 0.90.0__tar.gz → 0.91.0__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.
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/PKG-INFO +9 -8
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/README.md +7 -6
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/method_tree_request.py +47 -18
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/crate_ops.py +7 -7
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/pipeline.py +71 -31
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/validate.py +9 -5
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/schemas/models.py +33 -6
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/.gitignore +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/LICENSE +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/hatch_build.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/__init__.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/api.toml +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/api_config.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/blob_store.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/blob_upload.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/bundle.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/bundle_resources.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/exception_handlers.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/exceptions.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/json_body.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/limits.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/main.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/method_cache.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/method_source.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/middleware.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/openapi_responses.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/openapi_schema.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/problem_document.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/problem_view.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/py.typed +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/request_refusals.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/__init__.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/blob_upload.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/health.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/__init__.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/agent/__init__.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/agent/models.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/blobs.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/codegen.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/method_selection.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/pipe_io.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/resolve.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/tools.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/pipelex/utils.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/routes/version.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/run_extras_binding.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/schemas/__init__.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pipelex_api/security.py +0 -0
- {pipelex_api-0.90.0 → pipelex_api-0.91.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pipelex-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.91.0
|
|
4
4
|
Summary: Pipelex API
|
|
5
5
|
Project-URL: Homepage, https://pipelex.com
|
|
6
6
|
Project-URL: Repository, https://github.com/Pipelex/pipelex
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.14
|
|
17
17
|
Requires-Python: <3.15,>=3.11
|
|
18
18
|
Requires-Dist: fastapi<0.142,>=0.118.0
|
|
19
|
-
Requires-Dist: pipelex[anthropic,bedrock,fal,google,google-genai,mistralai]==0.
|
|
19
|
+
Requires-Dist: pipelex[anthropic,bedrock,fal,google,google-genai,mistralai]==0.91.0
|
|
20
20
|
Requires-Dist: pyjwt>=2.10.1
|
|
21
21
|
Requires-Dist: uvicorn>=0.37.0
|
|
22
22
|
Provides-Extra: dev
|
|
@@ -115,26 +115,27 @@ The API is now running at `http://localhost:8081`. To customize behavior (enable
|
|
|
115
115
|
|
|
116
116
|
# 🧪 Run your first pipeline
|
|
117
117
|
|
|
118
|
-
Once `/health` is green, send
|
|
118
|
+
Once `/health` is green, send a method and its inputs to `/v1/execute`. The method travels in `files`, a map from each file's path in the method's folder to that file's content. The example below summarizes a string with a method made of a single `.mthds` file, with no auth, ready to copy and paste:
|
|
119
119
|
|
|
120
120
|
```bash
|
|
121
121
|
curl -s http://localhost:8081/v1/execute \
|
|
122
122
|
-H "Content-Type: application/json" \
|
|
123
123
|
-d '{
|
|
124
|
-
"
|
|
125
|
-
|
|
124
|
+
"files": {
|
|
125
|
+
"summarize.mthds": "domain = \"hello\"\nmain_pipe = \"summarize\"\n\n[pipe.summarize]\ntype = \"PipeLLM\"\ndescription = \"Summarize the input text in one sentence\"\ninputs = { text = \"Text\" }\noutput = \"Text\"\nprompt = \"Summarize in one sentence:\\n@text\"\n"
|
|
126
|
+
},
|
|
126
127
|
"inputs": { "text": "Pipelex turns plain-language pipeline definitions into reproducible AI workflows that run as HTTP endpoints." }
|
|
127
128
|
}'
|
|
128
129
|
```
|
|
129
130
|
|
|
130
|
-
You'll get back a JSON response with `state: "COMPLETED"` and the summary under `pipe_output.working_memory.root.<main_stuff_name>.content`.
|
|
131
|
+
You'll get back a JSON response with `state: "COMPLETED"` and the summary under `pipe_output.working_memory.root.<main_stuff_name>.content`. The run starts at the method's `main_pipe`; to start at another pipe of the method, name it in `pipe_code`.
|
|
131
132
|
|
|
132
133
|
**Passing files (PDFs, images) as inputs.** Use the `Document` concept and point it at any HTTP(S) URL:
|
|
133
134
|
|
|
134
135
|
```json
|
|
135
136
|
{
|
|
136
137
|
"pipe_code": "your_pipe",
|
|
137
|
-
"
|
|
138
|
+
"files": { "your_method.mthds": "...your MTHDS..." },
|
|
138
139
|
"inputs": {
|
|
139
140
|
"cv": { "concept": "Document", "content": { "url": "https://example.com/resume.pdf" } }
|
|
140
141
|
}
|
|
@@ -143,7 +144,7 @@ You'll get back a JSON response with `state: "COMPLETED"` and the summary under
|
|
|
143
144
|
|
|
144
145
|
`Document` accepts public HTTP/HTTPS URLs, `pipelex-storage://` URIs, or base64 data URLs. For images, use the `Image` concept with the same `{ "url": "..." }` shape.
|
|
145
146
|
|
|
146
|
-
|
|
147
|
+
**Sending the method.** `files` holds the whole method, as the MTHDS standard's method tree: each key is a file's path relative to the method's folder, such as `summarize.mthds` or `templates/invoice.docx`, and each value is that file's content. A text file, such as a `.mthds` bundle or a Python module, is sent as its UTF-8 text in a JSON string, and a file that is not text, such as an office template or an image, as `{"base64": "..."}`. This server also keeps a blob store, which `GET /v1/version` reports as `blob_store: true`, so a client can send a file as `{"sha256": "...", "size": n}` instead: it asks `POST /v1/blobs/missing` which of its files the server lacks, uploads each of those to the target the answer names, and then sends the method with a hash entry for each file, so a method that has not changed is not uploaded again. The older `mthds_contents` field, a JSON array of `.mthds` texts, is still accepted, but it is being retired together with `bundle_b64` and `resources`, and a later release refuses them, so new clients send `files`. See the [Pipe Run page](https://docs.pipelex.com/latest/api-server/pipe-run/) for every supported input shape, the rules of [a method sent as a tree](https://docs.pipelex.com/latest/api-server/pipe-run/#sending-a-method-as-a-tree) and of [sending files by hash](https://docs.pipelex.com/latest/api-server/pipe-run/#sending-files-by-hash), and the full `/execute` reference.
|
|
147
148
|
|
|
148
149
|
# 📈 How to scale Pipelex
|
|
149
150
|
|
|
@@ -69,26 +69,27 @@ The API is now running at `http://localhost:8081`. To customize behavior (enable
|
|
|
69
69
|
|
|
70
70
|
# 🧪 Run your first pipeline
|
|
71
71
|
|
|
72
|
-
Once `/health` is green, send
|
|
72
|
+
Once `/health` is green, send a method and its inputs to `/v1/execute`. The method travels in `files`, a map from each file's path in the method's folder to that file's content. The example below summarizes a string with a method made of a single `.mthds` file, with no auth, ready to copy and paste:
|
|
73
73
|
|
|
74
74
|
```bash
|
|
75
75
|
curl -s http://localhost:8081/v1/execute \
|
|
76
76
|
-H "Content-Type: application/json" \
|
|
77
77
|
-d '{
|
|
78
|
-
"
|
|
79
|
-
|
|
78
|
+
"files": {
|
|
79
|
+
"summarize.mthds": "domain = \"hello\"\nmain_pipe = \"summarize\"\n\n[pipe.summarize]\ntype = \"PipeLLM\"\ndescription = \"Summarize the input text in one sentence\"\ninputs = { text = \"Text\" }\noutput = \"Text\"\nprompt = \"Summarize in one sentence:\\n@text\"\n"
|
|
80
|
+
},
|
|
80
81
|
"inputs": { "text": "Pipelex turns plain-language pipeline definitions into reproducible AI workflows that run as HTTP endpoints." }
|
|
81
82
|
}'
|
|
82
83
|
```
|
|
83
84
|
|
|
84
|
-
You'll get back a JSON response with `state: "COMPLETED"` and the summary under `pipe_output.working_memory.root.<main_stuff_name>.content`.
|
|
85
|
+
You'll get back a JSON response with `state: "COMPLETED"` and the summary under `pipe_output.working_memory.root.<main_stuff_name>.content`. The run starts at the method's `main_pipe`; to start at another pipe of the method, name it in `pipe_code`.
|
|
85
86
|
|
|
86
87
|
**Passing files (PDFs, images) as inputs.** Use the `Document` concept and point it at any HTTP(S) URL:
|
|
87
88
|
|
|
88
89
|
```json
|
|
89
90
|
{
|
|
90
91
|
"pipe_code": "your_pipe",
|
|
91
|
-
"
|
|
92
|
+
"files": { "your_method.mthds": "...your MTHDS..." },
|
|
92
93
|
"inputs": {
|
|
93
94
|
"cv": { "concept": "Document", "content": { "url": "https://example.com/resume.pdf" } }
|
|
94
95
|
}
|
|
@@ -97,7 +98,7 @@ You'll get back a JSON response with `state: "COMPLETED"` and the summary under
|
|
|
97
98
|
|
|
98
99
|
`Document` accepts public HTTP/HTTPS URLs, `pipelex-storage://` URIs, or base64 data URLs. For images, use the `Image` concept with the same `{ "url": "..." }` shape.
|
|
99
100
|
|
|
100
|
-
|
|
101
|
+
**Sending the method.** `files` holds the whole method, as the MTHDS standard's method tree: each key is a file's path relative to the method's folder, such as `summarize.mthds` or `templates/invoice.docx`, and each value is that file's content. A text file, such as a `.mthds` bundle or a Python module, is sent as its UTF-8 text in a JSON string, and a file that is not text, such as an office template or an image, as `{"base64": "..."}`. This server also keeps a blob store, which `GET /v1/version` reports as `blob_store: true`, so a client can send a file as `{"sha256": "...", "size": n}` instead: it asks `POST /v1/blobs/missing` which of its files the server lacks, uploads each of those to the target the answer names, and then sends the method with a hash entry for each file, so a method that has not changed is not uploaded again. The older `mthds_contents` field, a JSON array of `.mthds` texts, is still accepted, but it is being retired together with `bundle_b64` and `resources`, and a later release refuses them, so new clients send `files`. See the [Pipe Run page](https://docs.pipelex.com/latest/api-server/pipe-run/) for every supported input shape, the rules of [a method sent as a tree](https://docs.pipelex.com/latest/api-server/pipe-run/#sending-a-method-as-a-tree) and of [sending files by hash](https://docs.pipelex.com/latest/api-server/pipe-run/#sending-files-by-hash), and the full `/execute` reference.
|
|
101
102
|
|
|
102
103
|
# 📈 How to scale Pipelex
|
|
103
104
|
|
|
@@ -36,8 +36,8 @@ import base64
|
|
|
36
36
|
import json
|
|
37
37
|
import shutil
|
|
38
38
|
import tempfile
|
|
39
|
-
from collections.abc import Generator, Mapping
|
|
40
|
-
from contextlib import ExitStack, contextmanager
|
|
39
|
+
from collections.abc import AsyncGenerator, Generator, Mapping
|
|
40
|
+
from contextlib import ExitStack, asynccontextmanager, contextmanager
|
|
41
41
|
from pathlib import Path
|
|
42
42
|
from typing import Any, NamedTuple, NoReturn, cast
|
|
43
43
|
|
|
@@ -56,6 +56,7 @@ from pipelex.cli.installed_methods import PROVENANCE_FILENAME
|
|
|
56
56
|
from pipelex.config import is_pipe_func_sandbox_hosted
|
|
57
57
|
from pipelex.core.bundle_resources.bundle_resource import SUBMITTED_FILES_FIELD, root_escape_of
|
|
58
58
|
from pipelex.errors.registry.pipelex_codes import PipelexErrorCode
|
|
59
|
+
from pipelex.libraries.method_load_threads import run_method_load
|
|
59
60
|
from pipelex.libraries.written_trees import WRITTEN_METHOD_TREES, write_method_tree
|
|
60
61
|
from pipelex.methods.package_reason import MethodPackageReason
|
|
61
62
|
from pipelex.methods.structures_check import ensure_no_structured_content_in_library_sources, ensure_no_structured_content_python
|
|
@@ -778,6 +779,29 @@ class WrittenMethod(NamedTuple):
|
|
|
778
779
|
tree: MethodTree
|
|
779
780
|
|
|
780
781
|
|
|
782
|
+
def _write_method(*, request_method: RequestMethod, releases: ExitStack) -> WrittenMethod:
|
|
783
|
+
"""Write a method's trees into a temporary directory and hold each as written, each release pushed on `releases`.
|
|
784
|
+
|
|
785
|
+
The removal of the directory is pushed before anything is written, and each holding as soon as it is taken, so
|
|
786
|
+
closing `releases` undoes exactly what was done, however far the writing got. Blocking work.
|
|
787
|
+
"""
|
|
788
|
+
temp_dir = Path(tempfile.mkdtemp(prefix="pipelex-method-"))
|
|
789
|
+
releases.callback(shutil.rmtree, temp_dir, ignore_errors=True)
|
|
790
|
+
method_dir = temp_dir / "tree"
|
|
791
|
+
written = write_method_tree(contents=request_method.tree.contents, destination=method_dir, expected_digest=request_method.tree.tree.digest)
|
|
792
|
+
releases.enter_context(WRITTEN_METHOD_TREES.holding(tree=written))
|
|
793
|
+
methods_dirs: list[Path] = []
|
|
794
|
+
for index_dependency, (address, dependency) in enumerate(request_method.dependencies.items()):
|
|
795
|
+
methods_dir = temp_dir / f"methods-{index_dependency}"
|
|
796
|
+
dependency_dir = methods_dir / dependency_dir_name(address=address)
|
|
797
|
+
dependency_written = write_method_tree(contents=dependency.contents, destination=dependency_dir, expected_digest=dependency.tree.digest)
|
|
798
|
+
releases.enter_context(WRITTEN_METHOD_TREES.holding(tree=dependency_written))
|
|
799
|
+
if dependency.has_python and is_pipe_func_sandbox_hosted():
|
|
800
|
+
ensure_no_structured_content_python(package_dir=dependency_dir, package_address=address)
|
|
801
|
+
methods_dirs.append(methods_dir)
|
|
802
|
+
return WrittenMethod(method_dir=method_dir, methods_dirs=methods_dirs or None, tree=written)
|
|
803
|
+
|
|
804
|
+
|
|
781
805
|
@contextmanager
|
|
782
806
|
def written_method(*, request_method: RequestMethod) -> Generator[WrittenMethod, None, None]:
|
|
783
807
|
"""Write a method's trees into a temporary directory, hold each as written, and remove them all on the way out.
|
|
@@ -786,20 +810,25 @@ def written_method(*, request_method: RequestMethod) -> Generator[WrittenMethod,
|
|
|
786
810
|
`<name>/` in a methods directory of its own, the layout the engine's `methods_dirs` take, so two dependencies sharing
|
|
787
811
|
a name never meet. On a sandbox-hosted deployment, a dependency tree declaring a Python structure class is refused,
|
|
788
812
|
naming it by its address, as a fetched package is (`MethodStructuresRefusedError`, a 422).
|
|
813
|
+
|
|
814
|
+
Writing and removing the files is blocking work, done on the calling thread: asynchronous code uses
|
|
815
|
+
`written_method_async`.
|
|
789
816
|
"""
|
|
790
|
-
with ExitStack() as
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
yield
|
|
817
|
+
with ExitStack() as releases:
|
|
818
|
+
yield _write_method(request_method=request_method, releases=releases)
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
@asynccontextmanager
|
|
822
|
+
async def written_method_async(*, request_method: RequestMethod) -> AsyncGenerator[WrittenMethod, None]:
|
|
823
|
+
"""`written_method` for asynchronous code: the trees are written, and removed, in the method-load pool, off the event loop.
|
|
824
|
+
|
|
825
|
+
The directory is removed and its trees released whatever happens, a refusal while writing, a failure of the block
|
|
826
|
+
and a cancellation included. `run_method_load` lets a cancellation through only once its call has ended, or at once
|
|
827
|
+
when no thread has taken the call yet, so the removal never runs while the writing is still at work, and a
|
|
828
|
+
cancellation repeated during the removal waits for it too.
|
|
829
|
+
"""
|
|
830
|
+
releases = ExitStack()
|
|
831
|
+
try:
|
|
832
|
+
yield await run_method_load(_write_method, request_method=request_method, releases=releases)
|
|
833
|
+
finally:
|
|
834
|
+
await run_method_load(releases.close)
|
|
@@ -22,7 +22,6 @@ worker thread (`project_resolved_method`), never on the event loop: a slow resol
|
|
|
22
22
|
requests, and the current library, a context variable, is bound, read and cleared in that one thread's context.
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
import asyncio
|
|
26
25
|
from collections.abc import Callable
|
|
27
26
|
from enum import StrEnum
|
|
28
27
|
from typing import Literal, NamedTuple, NoReturn, TypeVar
|
|
@@ -34,6 +33,7 @@ from mthds.protocol.problem.registry import CoreCode
|
|
|
34
33
|
from pipelex.errors.problem import ProblemView
|
|
35
34
|
from pipelex.interpreter_hub import clear_current_library, get_current_library_id_or_none, get_library_manager, get_required_entry_pipe
|
|
36
35
|
from pipelex.libraries.library_crate import LibraryCrate
|
|
36
|
+
from pipelex.libraries.method_load_threads import run_method_load
|
|
37
37
|
from pipelex.libraries.pipe.exceptions import EntryPipeAmbiguousError, EntryPipeNotFoundError, PipeNotFoundError
|
|
38
38
|
from pipelex.libraries.pipe.pipe_suggestions import MAIN_PIPE_DECLARATION
|
|
39
39
|
from pipelex.methods.method_ref import looks_like_method_ref
|
|
@@ -160,11 +160,11 @@ async def project_resolved_method(
|
|
|
160
160
|
"""Resolve the method the request names into its normalized crate, and return what `project` makes of it.
|
|
161
161
|
|
|
162
162
|
The method is read as `/validate` reads it (`select_request_method`), then written out as a run writes it and loaded
|
|
163
|
-
alone as the caller's own library directory, its dependency trees as the packages it ships, in a worker thread
|
|
164
|
-
`project` runs in that thread with the freshly opened library loaded and
|
|
165
|
-
written tree held; both are released when it returns or raises. A
|
|
166
|
-
verdict instead, the `ValidateBundleError` carrying the structured
|
|
167
|
-
nothing left loaded.
|
|
163
|
+
alone as the caller's own library directory, its dependency trees as the packages it ships, in a worker thread of the
|
|
164
|
+
method-load pool (`run_method_load`). `project` runs in that thread with the freshly opened library loaded and
|
|
165
|
+
current, so it can read live pipes, and the written tree held; both are released when it returns or raises. A
|
|
166
|
+
method that does not resolve returns its invalid verdict instead, the `ValidateBundleError` carrying the structured
|
|
167
|
+
validation items, with `project` never called and nothing left loaded.
|
|
168
168
|
|
|
169
169
|
Raises (as problem documents): a 422 `request.unsupported` for a registry-form `method_ref`; the refusals of
|
|
170
170
|
`select_request_method`; the address-form fetch and location failures (`MethodRefError` subclasses); and whatever
|
|
@@ -184,7 +184,7 @@ async def project_resolved_method(
|
|
|
184
184
|
selected = await select_request_method(
|
|
185
185
|
request=request, method_ref=request_data.method_ref, resources=request_data.resources, read_scope=request_data.read_scope
|
|
186
186
|
)
|
|
187
|
-
return await
|
|
187
|
+
return await run_method_load(
|
|
188
188
|
_project_written_method,
|
|
189
189
|
request_method=selected.request_method,
|
|
190
190
|
method_label=selected.method_label,
|
|
@@ -16,14 +16,16 @@ from pipelex.core.pipes.pipe_output import PipeOutput
|
|
|
16
16
|
from pipelex.errors.bundle_validation_failure import BundleValidationFailure
|
|
17
17
|
from pipelex.errors.problem import ProblemView
|
|
18
18
|
from pipelex.errors.registry.pipelex_codes import PipelexErrorCode
|
|
19
|
+
from pipelex.libraries.method_load_threads import run_method_load
|
|
19
20
|
from pipelex.libraries.method_requirements import ensure_served_method_requirements_met
|
|
21
|
+
from pipelex.libraries.method_tree_listing import MethodTreeListing, scoped_tree_listing
|
|
20
22
|
from pipelex.pipe_run.delivery_assignment import DeliveryAssignment, StorageTarget, WebhookTarget
|
|
21
23
|
from pipelex.pipe_run.exceptions import AsyncExecutionNotEnabledError
|
|
22
24
|
from pipelex.pipe_run.pipe_run_protocol import PipeRunProtocol
|
|
23
25
|
from pipelex.pipeline.exceptions import ValidateBundleError
|
|
24
26
|
from pipelex.pipeline.pipeline_response import PipelexRunResultExecute, PipelexRunResultStart, RunState
|
|
25
27
|
from pipelex.pipeline.pipeline_run_setup import pipeline_run_setup
|
|
26
|
-
from pipelex.pipeline.runner import PipelexMTHDSProtocol
|
|
28
|
+
from pipelex.pipeline.runner import PipelexMTHDSProtocol, refuse_method_files
|
|
27
29
|
from pipelex.pipeline.validate_bundle_translation import translate_to_validate_bundle_error
|
|
28
30
|
from pipelex.reporting.usage_records import apply_tokens_usage_wire_shape
|
|
29
31
|
from pipelex.runtime_bridge.exceptions import MissingBundleValidatorError, MissingOrchestratorError
|
|
@@ -52,7 +54,7 @@ from pipelex_api.method_tree_request import (
|
|
|
52
54
|
select_tree_entry,
|
|
53
55
|
tree_documents_of,
|
|
54
56
|
with_resources,
|
|
55
|
-
|
|
57
|
+
written_method_async,
|
|
56
58
|
)
|
|
57
59
|
from pipelex_api.middleware import request_id_of
|
|
58
60
|
from pipelex_api.openapi_responses import (
|
|
@@ -81,6 +83,7 @@ from pipelex_api.security import AuthMode, get_auth_mode
|
|
|
81
83
|
if TYPE_CHECKING:
|
|
82
84
|
from collections.abc import AsyncGenerator
|
|
83
85
|
|
|
86
|
+
from mthds.method_tree.file_entry import MethodFiles
|
|
84
87
|
from mthds.protocol.pipe_output import VariableMultiplicity
|
|
85
88
|
from mthds.protocol.pipeline_inputs import PipelineInputs
|
|
86
89
|
from mthds.protocol.working_memory import WorkingMemoryAbstract
|
|
@@ -343,14 +346,17 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
343
346
|
async def execute(
|
|
344
347
|
self,
|
|
345
348
|
pipe_code: str | None = None,
|
|
346
|
-
|
|
349
|
+
files: MethodFiles | None = None,
|
|
347
350
|
inputs: PipelineInputs | WorkingMemoryAbstract[Any] | None = None,
|
|
348
351
|
output_name: str | None = None,
|
|
349
352
|
output_multiplicity: VariableMultiplicity | None = None,
|
|
350
353
|
dynamic_output_concept_ref: str | None = None,
|
|
351
354
|
extra: dict[str, Any] | None = None,
|
|
355
|
+
*,
|
|
356
|
+
mthds_contents: list[str] | None = None,
|
|
352
357
|
delivery_assignment: DeliveryAssignment | None = None,
|
|
353
358
|
request_id: str | None = None,
|
|
359
|
+
tree_listing: MethodTreeListing | None = None,
|
|
354
360
|
requested_orchestration_mode: str | None = None,
|
|
355
361
|
) -> PipelexRunResultExecute:
|
|
356
362
|
"""Execute a method synchronously, dispatching by the resolved `orchestration_mode`.
|
|
@@ -374,7 +380,14 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
374
380
|
`RunMetadata.request_id` for log correlation, so it reaches the job a worker is handed.
|
|
375
381
|
`requested_orchestration_mode` is the optional per-request backend override
|
|
376
382
|
(`PipelineApiExtras.orchestration_mode`).
|
|
383
|
+
|
|
384
|
+
The method's trees are listed once for the request: the check of its requirements lists them through
|
|
385
|
+
`tree_listing`, or a listing of its own when none is given, and the run loads them from that same listing.
|
|
377
386
|
"""
|
|
387
|
+
# The route resolves a method sent as `files` into a written tree before it calls this runner, which takes it
|
|
388
|
+
# as its library directory, so the map reaches the runner only from in-process code, and is refused as the
|
|
389
|
+
# base runner refuses it, before any backend is looked up.
|
|
390
|
+
refuse_method_files(files=files)
|
|
378
391
|
# Resolve the effective orchestration mode FIRST — a per-request override the deployment
|
|
379
392
|
# policy forbids is refused (422) here, before any library load / run registration. Mirrors start().
|
|
380
393
|
api_config = get_api_config()
|
|
@@ -382,7 +395,8 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
382
395
|
orchestrator = get_orchestrator_registry().get_optional(mode=orchestration_mode)
|
|
383
396
|
if orchestrator is None:
|
|
384
397
|
raise _missing_orchestrator_error(mode=orchestration_mode, api_config=api_config)
|
|
385
|
-
|
|
398
|
+
listing = tree_listing if tree_listing is not None else MethodTreeListing()
|
|
399
|
+
await self._ensure_served_method_requirements_met(tree_listing=listing)
|
|
386
400
|
|
|
387
401
|
# Dispatch the run through the mode-selected orchestrator by injecting it as this runner's
|
|
388
402
|
# PipeRun, then delegate to the base execute, which owns the full run lifecycle. The
|
|
@@ -391,6 +405,7 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
391
405
|
self._pipe_run = _OrchestratorPipeRun(orchestrator=orchestrator, orchestration_mode=orchestration_mode)
|
|
392
406
|
return await super().execute(
|
|
393
407
|
pipe_code=pipe_code,
|
|
408
|
+
files=files,
|
|
394
409
|
mthds_contents=mthds_contents,
|
|
395
410
|
inputs=inputs,
|
|
396
411
|
output_name=output_name,
|
|
@@ -399,18 +414,21 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
399
414
|
extra=extra,
|
|
400
415
|
delivery_assignment=delivery_assignment,
|
|
401
416
|
request_id=request_id,
|
|
417
|
+
tree_listing=listing,
|
|
402
418
|
)
|
|
403
419
|
|
|
404
420
|
@override
|
|
405
421
|
async def start(
|
|
406
422
|
self,
|
|
407
423
|
pipe_code: str | None = None,
|
|
408
|
-
|
|
424
|
+
files: MethodFiles | None = None,
|
|
409
425
|
inputs: PipelineInputs | WorkingMemoryAbstract[Any] | None = None,
|
|
410
426
|
output_name: str | None = None,
|
|
411
427
|
output_multiplicity: VariableMultiplicity | None = None,
|
|
412
428
|
dynamic_output_concept_ref: str | None = None,
|
|
413
429
|
extra: dict[str, Any] | None = None,
|
|
430
|
+
*,
|
|
431
|
+
mthds_contents: list[str] | None = None,
|
|
414
432
|
pipeline_run_id: str | None = None,
|
|
415
433
|
callback_urls: list[str] | None = None,
|
|
416
434
|
request_id: str | None = None,
|
|
@@ -451,6 +469,10 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
451
469
|
(`PipelineApiExtras.orchestration_mode`); it is resolved against the deployment's
|
|
452
470
|
`api.toml` policy and a forbidden override is refused with a 422.
|
|
453
471
|
"""
|
|
472
|
+
# The route resolves a method sent as `files` into a written tree before it calls this runner, which takes it
|
|
473
|
+
# as its library directory, so the map reaches the runner only from in-process code, and is refused as the
|
|
474
|
+
# base runner refuses it.
|
|
475
|
+
refuse_method_files(files=files)
|
|
454
476
|
if extra:
|
|
455
477
|
# Nothing on the wire reaches `extra`: the route parses the extras itself and passes them by name, so a
|
|
456
478
|
# value here is a fault of the in-process code calling this runner, never the caller's.
|
|
@@ -472,7 +494,10 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
472
494
|
f"async-capable orchestration, and this deployment has none. Use /execute (synchronous) instead."
|
|
473
495
|
)
|
|
474
496
|
raise AsyncExecutionNotEnabledError(msg)
|
|
475
|
-
|
|
497
|
+
# The method's trees are listed once for the request: the requirements check lists them, and the job's load
|
|
498
|
+
# takes them from the same listing.
|
|
499
|
+
tree_listing = MethodTreeListing()
|
|
500
|
+
await self._ensure_served_method_requirements_met(tree_listing=tree_listing)
|
|
476
501
|
created_at = get_current_iso_timestamp()
|
|
477
502
|
pipelex_inputs: PipelineInputs | WorkingMemory | None = cast("PipelineInputs | WorkingMemory | None", inputs)
|
|
478
503
|
|
|
@@ -506,6 +531,7 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
506
531
|
submitted_files=self.submitted_files,
|
|
507
532
|
pipeline_run_id=pipeline_run_id,
|
|
508
533
|
request_id=request_id,
|
|
534
|
+
tree_listing=tree_listing,
|
|
509
535
|
)
|
|
510
536
|
|
|
511
537
|
delivery_assignment = DeliveryAssignment(
|
|
@@ -554,13 +580,15 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
554
580
|
crate_fingerprint=pipe_job.job_metadata.run_metadata.crate_fingerprint,
|
|
555
581
|
)
|
|
556
582
|
|
|
557
|
-
async def _ensure_served_method_requirements_met(self) -> None:
|
|
583
|
+
async def _ensure_served_method_requirements_met(self, *, tree_listing: MethodTreeListing) -> None:
|
|
558
584
|
"""Refuse the request's method when its requirements.txt is not met, before anything loads or is dispatched.
|
|
559
585
|
|
|
560
586
|
This runner's `library_dirs` are the directories it wrote the request's method tree into, the caller's own,
|
|
561
587
|
whose load reports a requirements refusal as the caller's too; judging it here first refuses the run before
|
|
562
588
|
any library opens or any run is registered, naming no path on this host. The tree is held as written, so its
|
|
563
|
-
listing is the tree the request sent. The listing reads and hashes files, so it runs off the event loop
|
|
589
|
+
listing is the tree the request sent. The listing reads and hashes files, so it runs off the event loop, in the method-load pool,
|
|
590
|
+
and it is made through `tree_listing`, which the caller hands to the request's load, so the load reads the trees
|
|
591
|
+
checked here without listing them again.
|
|
564
592
|
|
|
565
593
|
Raises:
|
|
566
594
|
MethodRequirementsFileError: The method's requirements.txt holds a line the standard refuses.
|
|
@@ -571,7 +599,9 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
571
599
|
if not self.library_dirs:
|
|
572
600
|
return
|
|
573
601
|
library_dirs = [Path(library_dir) for library_dir in self.library_dirs]
|
|
574
|
-
|
|
602
|
+
# In the method-load pool, whose cancellation waits for the check to end: the directories it lists are removed
|
|
603
|
+
# when the request's scope exits, which a client disconnect can make happen while the check still hashes them.
|
|
604
|
+
await run_method_load(ensure_served_method_requirements_met, library_dirs=library_dirs, tree_listing=tree_listing)
|
|
575
605
|
|
|
576
606
|
async def validate_verdict(
|
|
577
607
|
self,
|
|
@@ -621,6 +651,10 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
621
651
|
arm hands the method's trees to its worker through. The route builds a validating runner with the
|
|
622
652
|
`read_scope` its request names, `None` when it names none, and never with the caller's id a run
|
|
623
653
|
would fall back to: nothing lists or erases what a validation would leave under that prefix.
|
|
654
|
+
|
|
655
|
+
The method's trees are listed once for the request: the requirements check lists them, and the validation is
|
|
656
|
+
called with that listing in scope (`scoped_tree_listing`), which the in-process validator loads the trees from,
|
|
657
|
+
while a validator dispatching to a worker never reads it.
|
|
624
658
|
"""
|
|
625
659
|
# Resolve the effective mode FIRST — a per-request override the deployment policy forbids
|
|
626
660
|
# is refused (422) here, before any validator dispatch / library load. Mirrors start().
|
|
@@ -631,33 +665,39 @@ class ApiRunner(PipelexMTHDSProtocol):
|
|
|
631
665
|
raise _missing_bundle_validator_error(mode=orchestration_mode, api_config=api_config)
|
|
632
666
|
library_dirs = [Path(library_dir) for library_dir in self.library_dirs] if self.library_dirs else None
|
|
633
667
|
# The method's requirements.txt is judged here, on the runner, which holds the method's files and the package set
|
|
634
|
-
# its PipeFunc steps run with, before any dispatch
|
|
635
|
-
#
|
|
668
|
+
# its PipeFunc steps run with, before any dispatch, so a refusal reaches the caller whichever validator runs. A
|
|
669
|
+
# worker a method tree's validation is dispatched to never reads these directories: it receives the method's
|
|
670
|
+
# trees through its method file store and judges their requirements again as it loads them. A refusal is the
|
|
671
|
+
# method's invalid verdict, as a run of it is refused before its files load.
|
|
672
|
+
tree_listing = MethodTreeListing()
|
|
636
673
|
try:
|
|
637
674
|
with translate_to_validate_bundle_error():
|
|
638
|
-
await self._ensure_served_method_requirements_met()
|
|
675
|
+
await self._ensure_served_method_requirements_met(tree_listing=tree_listing)
|
|
639
676
|
except ValidateBundleError as invalid_method:
|
|
640
677
|
return BundleValidationFailure(
|
|
641
678
|
problem=invalid_method.to_problem(view=ProblemView.OPERATOR),
|
|
642
679
|
validation_errors=invalid_method.validation_error_items(),
|
|
643
680
|
)
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
681
|
+
# The validator seam takes no listing: the one the requirements check made is in scope around the call, so the
|
|
682
|
+
# in-process validator loads the trees it checked without listing them again.
|
|
683
|
+
with scoped_tree_listing(tree_listing=tree_listing):
|
|
684
|
+
verdict = await validator.validate_bundles(
|
|
685
|
+
mthds_contents=mthds_contents,
|
|
686
|
+
mthds_sources=mthds_sources,
|
|
687
|
+
allow_signatures=allow_signatures,
|
|
688
|
+
# A method directory is the whole method: no library directory loads beside it.
|
|
689
|
+
library_dirs=None if method_dir is not None else library_dirs,
|
|
690
|
+
method_dir=method_dir,
|
|
691
|
+
methods_dirs=self.methods_dirs if method_dir is not None else None,
|
|
692
|
+
# A validation is not a run, but its dry runs and its `pipe_dry_run` event are still
|
|
693
|
+
# done for the caller this runner was built for — the user and groups a run would state.
|
|
694
|
+
caller_identity=self.caller_identity,
|
|
695
|
+
graph_pipe_code=graph_pipe_code,
|
|
696
|
+
# The files submitted with the contents, which the validation's dry run reads, the template check included.
|
|
697
|
+
submitted_files=self.submitted_files,
|
|
698
|
+
# The scope the host named, under which a dispatched validator's worker reads the method's trees.
|
|
699
|
+
read_scope=self.read_scope,
|
|
700
|
+
)
|
|
661
701
|
# The core seam types its valid arm at the protocol-level ValidationReport (a leaf type)
|
|
662
702
|
# to stay import-acyclic in core; every registered validator in fact produces the canonical
|
|
663
703
|
# PipelexValidationReport. Recover the precise type here — the single narrowing point — so
|
|
@@ -874,7 +914,7 @@ async def _request_method_of(*, run_request: RunRequest, body: bytes, blob_store
|
|
|
874
914
|
async def _run_source(*, run_request: RunRequest, body: bytes, blob_scope: str) -> AsyncGenerator[_ResolvedRunSource, None]:
|
|
875
915
|
"""Resolve the request's run source: a method tree (`files`, `bundle_b64` or a `method_ref`), or the inline contents.
|
|
876
916
|
|
|
877
|
-
A method tree is written into a temporary directory and held as written, then handed to the runner as its one
|
|
917
|
+
A method tree is written into a temporary directory in a worker thread and held as written, then handed to the runner as its one
|
|
878
918
|
library directory, the caller's own, with the dependency trees it carries as `methods_dirs`: the load reads the
|
|
879
919
|
tree whole, its `.mthds` files as the method's bundles and every other file as one a step may read, such as a
|
|
880
920
|
`PipeDocGen` template or a `PipeFunc` module, and names each by its tree path. A refusal while loading it is the
|
|
@@ -912,7 +952,7 @@ async def _run_source(*, run_request: RunRequest, body: bytes, blob_scope: str)
|
|
|
912
952
|
)
|
|
913
953
|
return
|
|
914
954
|
pipe_code = run_request.pipe_code or select_tree_entry(tree=request_method.tree, method_label=method_label).pipe_ref
|
|
915
|
-
with
|
|
955
|
+
async with written_method_async(request_method=request_method) as written:
|
|
916
956
|
yield _ResolvedRunSource(
|
|
917
957
|
mthds_contents=None,
|
|
918
958
|
library_dirs=[str(written.method_dir)],
|
|
@@ -21,7 +21,7 @@ from pipelex_api.bundle_resources import decode_bundle_resources
|
|
|
21
21
|
from pipelex_api.exception_handlers import problem_response
|
|
22
22
|
from pipelex_api.exceptions import RequestFieldConflictError
|
|
23
23
|
from pipelex_api.json_body import JsonBodyRoute
|
|
24
|
-
from pipelex_api.method_tree_request import RequestMethod, select_tree_entry,
|
|
24
|
+
from pipelex_api.method_tree_request import RequestMethod, select_tree_entry, written_method_async
|
|
25
25
|
from pipelex_api.middleware import request_id_of
|
|
26
26
|
from pipelex_api.openapi_responses import PROBLEM_403_READ_SCOPE, PROBLEM_404_METHOD_PACKAGE, PROBLEM_500_HELD_BLOBS, PROBLEM_501_RUN_POLICY
|
|
27
27
|
from pipelex_api.problem_view import problem_view_of
|
|
@@ -79,7 +79,10 @@ def _resolve_validation_views(views: list[str]) -> set[ValidationView]:
|
|
|
79
79
|
|
|
80
80
|
|
|
81
81
|
class ValidateRequest(MthdsContentsRequest, CallerAnalyticsGroupsMixin):
|
|
82
|
-
"""The
|
|
82
|
+
"""The method to validate, in `files`, by `method_ref` or as `mthds_contents`, plus `allow_signatures` and optional per-file sources.
|
|
83
|
+
|
|
84
|
+
`mthds_contents`, and the `mthds_sources` and `resources` that serve it, are being retired: a later release
|
|
85
|
+
refuses them, and a method is sent in `files`, whose bundles are named by their tree paths.
|
|
83
86
|
|
|
84
87
|
`mthds_sources`, when provided, pairs each `mthds_contents[i]` with a logical source (e.g. the
|
|
85
88
|
file's path relative to the submitted directory). The runner threads it onto
|
|
@@ -91,7 +94,8 @@ class ValidateRequest(MthdsContentsRequest, CallerAnalyticsGroupsMixin):
|
|
|
91
94
|
mthds_sources: list[str] | None = Field(
|
|
92
95
|
default=None,
|
|
93
96
|
description=(
|
|
94
|
-
"
|
|
97
|
+
"Being retired with `mthds_contents`, and refused by a later release: the bundles of a method sent in `files` "
|
|
98
|
+
"are named by their tree paths. Optional per-file sources, parallel to `mthds_contents`. When provided, each entry is threaded "
|
|
95
99
|
"onto the corresponding bundle's `source` so server-side validation errors carry a `source` pointing "
|
|
96
100
|
"at the owning file. Must match `mthds_contents` in length when present."
|
|
97
101
|
),
|
|
@@ -366,7 +370,7 @@ async def validate_mthds(request: Request, request_data: ValidateRequest) -> JSO
|
|
|
366
370
|
- **Either arm:** a member with no value is absent rather than `null`, at the arm's top level and in its
|
|
367
371
|
problem items; the structured artifacts of the valid arm keep their own serialization.
|
|
368
372
|
- **No verdict (non-2xx):** a malformed request body, an `mthds_sources` length mismatch, or
|
|
369
|
-
|
|
373
|
+
more or fewer than one of `files` / `method_ref` / `mthds_contents` is a request-shape **422**, and so is a forbidden
|
|
370
374
|
`orchestration_mode` override; a host-wiring programmer error or a genuine orchestrator fault is a
|
|
371
375
|
**5xx**; auth is **401/403**. A `method_ref` **resolution failure** is also a no-verdict condition —
|
|
372
376
|
never an `is_valid: false`: each answers the status its code declares, such as a **422** for a
|
|
@@ -425,7 +429,7 @@ async def validate_mthds(request: Request, request_data: ValidateRequest) -> JSO
|
|
|
425
429
|
entry = _optional_tree_entry(request_method=request_method, method_label=method_label)
|
|
426
430
|
tree_default_pipe_ref = entry.pipe_ref if entry is not None else None
|
|
427
431
|
default_pipe_ref_known = True
|
|
428
|
-
with
|
|
432
|
+
async with written_method_async(request_method=request_method) as written:
|
|
429
433
|
verdict = await ApiRunner(
|
|
430
434
|
library_dirs=[str(written.method_dir)],
|
|
431
435
|
library_dirs_are_callers=True,
|
|
@@ -88,7 +88,7 @@ DEPENDENCIES_FIELD_DESCRIPTION = (
|
|
|
88
88
|
"by. They are searched before the runner's installed copies, for this request alone. Accompanies `files`."
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
_RESOURCES_FIELD_FACTS = (
|
|
92
92
|
"The files submitted with the bundle that its steps read, such as a PipeDocGen step's `template_file` (an "
|
|
93
93
|
"`.xlsx`, `.docx`, `.pptx` or `.html` template): a JSON object mapping each file's bundle-relative path, exactly "
|
|
94
94
|
"as the step names it, to the file's bytes in standard base64. A path must be relative and written with forward "
|
|
@@ -102,6 +102,21 @@ RESOURCES_FIELD_DESCRIPTION = (
|
|
|
102
102
|
"names by its digest: no file's bytes travel with the run, each being read where a step uses it."
|
|
103
103
|
)
|
|
104
104
|
|
|
105
|
+
RESOURCES_FIELD_DESCRIPTION = (
|
|
106
|
+
f"{_RESOURCES_FIELD_FACTS} Being retired with `mthds_contents`, and refused by a later release: send these files in "
|
|
107
|
+
"the method's tree, in `files`."
|
|
108
|
+
)
|
|
109
|
+
"""`resources` on the routes that also take `mthds_contents`, the run routes and `/validate`, with which it is retired."""
|
|
110
|
+
|
|
111
|
+
TREE_ROUTE_RESOURCES_FIELD_DESCRIPTION = (
|
|
112
|
+
f"{_RESOURCES_FIELD_FACTS} Being retired, and refused by a later release: a file the steps read travels inside the "
|
|
113
|
+
"method's tree, in `files`, at its path beside the bundle that names it."
|
|
114
|
+
)
|
|
115
|
+
"""`resources` on the routes that take a method only as `files` or `method_ref`: `/resolve`, `/codegen` and `/pipe-io`."""
|
|
116
|
+
|
|
117
|
+
MTHDS_CONTENTS_RETIRING_NOTE = "Being retired, and refused by a later release: send the method in `files`."
|
|
118
|
+
"""Opens the description of every request field that takes a method's bundles as `mthds_contents`."""
|
|
119
|
+
|
|
105
120
|
|
|
106
121
|
class RunRequest(BaseModel):
|
|
107
122
|
"""Body of `POST /execute` — this server's typed request model.
|
|
@@ -124,7 +139,8 @@ class RunRequest(BaseModel):
|
|
|
124
139
|
|
|
125
140
|
Attributes:
|
|
126
141
|
pipe_code: Code of the pipe to execute.
|
|
127
|
-
mthds_contents: List of MTHDS bundle contents to load.
|
|
142
|
+
mthds_contents: List of MTHDS bundle contents to load. Being retired, with `bundle_b64`
|
|
143
|
+
and `resources`, in favor of `files`.
|
|
128
144
|
inputs: Inputs in PipelineInputs format, as plain JSON — Pydantic validation is
|
|
129
145
|
skipped to preserve the flexible format (strings, lists, objects); the runtime
|
|
130
146
|
shapes each value against the entry pipe's declared input.
|
|
@@ -154,7 +170,14 @@ class RunRequest(BaseModel):
|
|
|
154
170
|
model_config = ConfigDict(extra="allow")
|
|
155
171
|
|
|
156
172
|
pipe_code: str | None = None
|
|
157
|
-
mthds_contents: list[str] | None =
|
|
173
|
+
mthds_contents: list[str] | None = Field(
|
|
174
|
+
default=None,
|
|
175
|
+
description=(
|
|
176
|
+
f"{MTHDS_CONTENTS_RETIRING_NOTE} The method's `.mthds` files as raw strings, always an array, even for a single "
|
|
177
|
+
"file; with no `pipe_code`, a file must declare `main_pipe`. Mutually exclusive with `files`, `bundle_b64` and "
|
|
178
|
+
"`method_ref`."
|
|
179
|
+
),
|
|
180
|
+
)
|
|
158
181
|
inputs: Annotated[PipelineInputs | None, SkipValidation] = None
|
|
159
182
|
output_name: str | None = None
|
|
160
183
|
output_multiplicity: VariableMultiplicity | None = None
|
|
@@ -166,7 +189,8 @@ class RunRequest(BaseModel):
|
|
|
166
189
|
"map of its entries, so the archive's names are judged by the method tree's rule as the keys of `files` are. "
|
|
167
190
|
"The method packages it calls by address may travel with it under `.mthds/methods/<name>/`, each with its "
|
|
168
191
|
"`METHODS.toml`; they are searched before the runner's installed copies, for this run alone. "
|
|
169
|
-
"Mutually exclusive with `files`. Custom `.py` is only honored on a sandbox-hosted deployment."
|
|
192
|
+
"Mutually exclusive with `files`. Custom `.py` is only honored on a sandbox-hosted deployment. Being retired "
|
|
193
|
+
"with `mthds_contents`, and refused by a later release: send the method in `files`."
|
|
170
194
|
),
|
|
171
195
|
)
|
|
172
196
|
# Documents the wire only: the map is read from the request's raw JSON text (`pipelex_api.method_tree_request`),
|
|
@@ -660,7 +684,7 @@ class MethodTreeRequest(BaseModel):
|
|
|
660
684
|
"until a method registry exists). Exactly one of `files` / `method_ref`."
|
|
661
685
|
),
|
|
662
686
|
)
|
|
663
|
-
resources: dict[str, str] | None = Field(default=None, description=
|
|
687
|
+
resources: dict[str, str] | None = Field(default=None, description=TREE_ROUTE_RESOURCES_FIELD_DESCRIPTION)
|
|
664
688
|
read_scope: str | None = Field(default=None, description=_TREE_READ_SCOPE_DESCRIPTION)
|
|
665
689
|
|
|
666
690
|
@field_validator("read_scope")
|
|
@@ -735,7 +759,10 @@ class MthdsContentsRequest(BaseModel):
|
|
|
735
759
|
default=None,
|
|
736
760
|
min_length=1,
|
|
737
761
|
max_length=MAX_MTHDS_FILES_PER_REQUEST,
|
|
738
|
-
description=
|
|
762
|
+
description=(
|
|
763
|
+
f"{MTHDS_CONTENTS_RETIRING_NOTE} MTHDS contents to load (always an array, even for a single file). "
|
|
764
|
+
"Exactly one of `mthds_contents` / `files` / `method_ref`."
|
|
765
|
+
),
|
|
739
766
|
)
|
|
740
767
|
# Documents the wire only: the route reads the map from the request's raw JSON text, never from this field.
|
|
741
768
|
files: Annotated[dict[str, FileEntry] | None, SkipValidation] = Field(
|
|
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
|