pipelex-api 0.71.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pipelex_api/__init__.py +0 -0
- pipelex_api/api.toml +21 -0
- pipelex_api/api_config.py +144 -0
- pipelex_api/bundle.py +243 -0
- pipelex_api/disclosure.py +43 -0
- pipelex_api/error_types.py +80 -0
- pipelex_api/error_uri.py +45 -0
- pipelex_api/errors.py +130 -0
- pipelex_api/exception_handlers.py +693 -0
- pipelex_api/json_body.py +182 -0
- pipelex_api/limits.py +67 -0
- pipelex_api/main.py +221 -0
- pipelex_api/method_cache.py +241 -0
- pipelex_api/method_source.py +215 -0
- pipelex_api/middleware.py +209 -0
- pipelex_api/openapi_responses.py +186 -0
- pipelex_api/openapi_schema.py +83 -0
- pipelex_api/problem_document.py +134 -0
- pipelex_api/py.typed +0 -0
- pipelex_api/routes/__init__.py +23 -0
- pipelex_api/routes/health.py +24 -0
- pipelex_api/routes/pipelex/__init__.py +21 -0
- pipelex_api/routes/pipelex/agent/__init__.py +11 -0
- pipelex_api/routes/pipelex/agent/concept.py +60 -0
- pipelex_api/routes/pipelex/agent/models.py +49 -0
- pipelex_api/routes/pipelex/agent/pipe_spec.py +59 -0
- pipelex_api/routes/pipelex/build/__init__.py +11 -0
- pipelex_api/routes/pipelex/build/inputs.py +192 -0
- pipelex_api/routes/pipelex/build/output.py +163 -0
- pipelex_api/routes/pipelex/build/runner.py +236 -0
- pipelex_api/routes/pipelex/codegen.py +164 -0
- pipelex_api/routes/pipelex/crate_ops.py +331 -0
- pipelex_api/routes/pipelex/pipe_io.py +186 -0
- pipelex_api/routes/pipelex/pipeline.py +938 -0
- pipelex_api/routes/pipelex/resolve.py +81 -0
- pipelex_api/routes/pipelex/tools.py +111 -0
- pipelex_api/routes/pipelex/utils.py +6 -0
- pipelex_api/routes/pipelex/validate.py +473 -0
- pipelex_api/routes/version.py +51 -0
- pipelex_api/schemas/__init__.py +0 -0
- pipelex_api/schemas/models.py +653 -0
- pipelex_api/security.py +284 -0
- pipelex_api-0.71.0.dist-info/METADATA +188 -0
- pipelex_api-0.71.0.dist-info/RECORD +46 -0
- pipelex_api-0.71.0.dist-info/WHEEL +4 -0
- pipelex_api-0.71.0.dist-info/licenses/LICENSE +95 -0
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, Union
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
from fastapi.responses import JSONResponse
|
|
5
|
+
from pipelex.interpreter_hub import get_own_pipes, get_pipe_library
|
|
6
|
+
from pipelex.pipeline.build_pipe_io_artifacts import build_pipe_io_artifacts
|
|
7
|
+
from pipelex.pipeline.exceptions import ValidateBundleError
|
|
8
|
+
from pipelex.pipeline.input_form import InputForm, OutputForm
|
|
9
|
+
from pipelex.pipeline.pipe_io_contracts import PipeIOContracts
|
|
10
|
+
from pipelex.pipeline.validate_bundle import build_pending_signatures
|
|
11
|
+
from pipelex.tools.typing.pydantic_utils import empty_list_factory_of
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from pipelex_api.json_body import JsonBodyRoute
|
|
15
|
+
from pipelex_api.openapi_responses import PROBLEM_404_METHOD_PACKAGE, PROBLEM_501_METHOD_REF
|
|
16
|
+
from pipelex_api.routes.pipelex.crate_ops import (
|
|
17
|
+
CrateInvalidReport,
|
|
18
|
+
RequestedPipe,
|
|
19
|
+
invalid_crate_report_response,
|
|
20
|
+
resolve_requested_crate,
|
|
21
|
+
resolve_requested_pipe,
|
|
22
|
+
select_default_pipe,
|
|
23
|
+
teardown_current_library,
|
|
24
|
+
)
|
|
25
|
+
from pipelex_api.schemas.models import MthdsFileItem, MthdsPipeRequest
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from pipelex.pipe_machinery.pipe_abstract import PipeAbstract
|
|
29
|
+
|
|
30
|
+
router = APIRouter(tags=["pipe-io"], route_class=JsonBodyRoute)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PipeIORequest(MthdsPipeRequest):
|
|
34
|
+
"""The pipe I/O request: the crate routes' closure selector and pipe selector, plus the whole-method and echo opt-ins.
|
|
35
|
+
|
|
36
|
+
The hosted catalog selector is replaced by `files` before the request reaches the runner, so this
|
|
37
|
+
request has no field for it. It takes no `views` either, since the valid arm always carries all
|
|
38
|
+
three artifacts.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
# This docstring is published as the schema's description, so it must never name the hosted
|
|
42
|
+
# selector field: the runner's OpenAPI declares no such field, not even in prose, and
|
|
43
|
+
# `test_openapi_contract.py` pins the whole document free of that name.
|
|
44
|
+
|
|
45
|
+
all_pipes: bool = Field(
|
|
46
|
+
default=False,
|
|
47
|
+
description=(
|
|
48
|
+
"Describe every pipe the closure loads instead of the selected one. The artifact maps are then keyed by every "
|
|
49
|
+
"pipe, and the route never refuses for want of an entry pipe: `pipe_ref` on the valid arm is the requested ref, "
|
|
50
|
+
"else the method's own entry pipe, else `null`."
|
|
51
|
+
),
|
|
52
|
+
)
|
|
53
|
+
include_files: bool = Field(
|
|
54
|
+
default=False,
|
|
55
|
+
description=(
|
|
56
|
+
"Echo the resolved closure's `.mthds` files on the valid arm, in the request's own `files[]` shape: the request's "
|
|
57
|
+
"files for inline `files[]`, the fetched package's `.mthds` files under their package-relative paths for a "
|
|
58
|
+
"`method_ref`. Absent from the response unless true."
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PipeIOValidReport(BaseModel):
|
|
64
|
+
"""The 200 **valid** arm: the method's three I/O artifacts, with the selection and the runnability facts beside them.
|
|
65
|
+
|
|
66
|
+
The three maps share one key set: the resolved `pipe_ref` alone by default, every pipe the
|
|
67
|
+
closure loads under `all_pipes`. Each map is the standard's artifact under its neutral name, and
|
|
68
|
+
for a closure `/validate` also accepts it equals `/validate`'s same-named field restricted to the
|
|
69
|
+
same keys — both routes call the one builder, and both dump without dropping null members.
|
|
70
|
+
`is_valid: true` means what it means on `/resolve`: the closure parsed, loaded and passed static
|
|
71
|
+
validation. No dry run ran; that verdict stays `/validate`'s.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
is_valid: Literal[True] = True
|
|
75
|
+
pipe_ref: str | None = Field(
|
|
76
|
+
...,
|
|
77
|
+
description=(
|
|
78
|
+
"The qualified `domain.pipe_code` the selection resolved, read back off the resolved pipe and never echoed from "
|
|
79
|
+
"the request. `null` only under `all_pipes` when no pipe was requested and the method declares no single entry pipe."
|
|
80
|
+
),
|
|
81
|
+
)
|
|
82
|
+
pipe_io_contracts: PipeIOContracts = Field(..., description="The standard's pipe I/O contracts, keyed by qualified `pipe_ref`.")
|
|
83
|
+
input_form: InputForm = Field(..., description="The standard's input-form descriptors, keyed by qualified `pipe_ref`.")
|
|
84
|
+
output_form: OutputForm = Field(..., description="The standard's output-form descriptors, keyed by qualified `pipe_ref`.")
|
|
85
|
+
default_pipe_ref: str | None = Field(
|
|
86
|
+
...,
|
|
87
|
+
description=(
|
|
88
|
+
"The method's own entry pipe: the selection chain without the request's `pipe_ref` — the fetched package "
|
|
89
|
+
"manifest's `main_pipe`, else the closure's single `main_pipe` declaration. A stated `null` when that chain finds "
|
|
90
|
+
"none or several. Not `/validate`'s field of the same name, which is the run default."
|
|
91
|
+
),
|
|
92
|
+
)
|
|
93
|
+
pending_signatures: list[str] = Field(
|
|
94
|
+
...,
|
|
95
|
+
description="The qualified refs of every pipe of the closure still declared as a signature, exactly as on `/validate`.",
|
|
96
|
+
)
|
|
97
|
+
is_runnable: bool = Field(..., description="`not pending_signatures`, exactly as on `/validate`. No dry run backs it.")
|
|
98
|
+
# Never serialized from the model: the route attaches the echo itself, and only when asked, so the
|
|
99
|
+
# field is absent — not empty, not null — otherwise. Declared here so the published schema names it.
|
|
100
|
+
files: list[MthdsFileItem] = Field(
|
|
101
|
+
default_factory=empty_list_factory_of(MthdsFileItem),
|
|
102
|
+
description="The resolved closure's `.mthds` files, in the request's `files[]` shape. Present only with `include_files: true`.",
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
# Discriminated 200 response union: a consumer pattern-matches the one mandatory `is_valid`
|
|
107
|
+
# field to learn the verdict — the same discipline as `POST /validate` and `POST /resolve`.
|
|
108
|
+
PipeIOResponse = Annotated[Union[PipeIOValidReport, CrateInvalidReport], Field(discriminator="is_valid")]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@router.post(
|
|
112
|
+
"/pipe-io",
|
|
113
|
+
response_model=PipeIOResponse,
|
|
114
|
+
# On top of the composite router's shared 401/413/422/500: the `method_ref` fetch outcomes it
|
|
115
|
+
# shares with `/resolve`. No structures 403: the route reads only a package's `.mthds` files.
|
|
116
|
+
responses={404: PROBLEM_404_METHOD_PACKAGE, 501: PROBLEM_501_METHOD_REF},
|
|
117
|
+
# NOT tagged `x-mthds-protocol`: it carries the standard's artifacts under their neutral names,
|
|
118
|
+
# but the route is a Pipelex API extension, and the flag marks the standard's five operations alone.
|
|
119
|
+
)
|
|
120
|
+
async def pipe_io(request_data: PipeIORequest) -> JSONResponse:
|
|
121
|
+
"""Return a method's pipe I/O contracts, input form and output form, with no dry run (Pipelex API extension).
|
|
122
|
+
|
|
123
|
+
The closure resolves through the static core `/resolve` rides, a pipe is selected the way the
|
|
124
|
+
per-pipe routes select one, and the three artifacts are derived with `build_pipe_io_artifacts`,
|
|
125
|
+
the builder `/validate` and every run call — so a call costs one load and one derivation where
|
|
126
|
+
`/validate` dry-runs every pipe.
|
|
127
|
+
|
|
128
|
+
Response contract (the `/validate` discipline):
|
|
129
|
+
|
|
130
|
+
- **Valid verdict (200, `is_valid: true`):** the three artifact maps keyed by qualified `pipe_ref`,
|
|
131
|
+
beside `pipe_ref`, `default_pipe_ref`, `pending_signatures`, `is_runnable`, and `files` when
|
|
132
|
+
`include_files` is true.
|
|
133
|
+
- **Invalid verdict (200, `is_valid: false`):** the closure could not be parsed, loaded, or
|
|
134
|
+
statically validated — the crate verdict, carrying no artifact, no selection and no files.
|
|
135
|
+
- **No verdict (non-2xx):** a malformed body and neither or both closure selectors are
|
|
136
|
+
request-shape 422s (`ValidationError`); a selection refusal is an input 422 named for its failure,
|
|
137
|
+
`EntryPipeNotFoundError` for an unknown `pipe_ref` or a single-pipe request whose chain finds no
|
|
138
|
+
entry pipe, `EntryPipeAmbiguousError` for an ambiguous one or a chain that finds several; the
|
|
139
|
+
`method_ref` fetch outcomes are those of `/resolve` (404, 422, 501);
|
|
140
|
+
an artifact that cannot be derived (`PipeIOContractError`) is a 500. All RFC 7807
|
|
141
|
+
`application/problem+json` via the global handlers.
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
resolved = resolve_requested_crate(request_data)
|
|
145
|
+
except ValidateBundleError as validate_error:
|
|
146
|
+
return invalid_crate_report_response(validate_error.to_error_report())
|
|
147
|
+
try:
|
|
148
|
+
default_pipe = select_default_pipe(resolved.crate, manifest_main_pipe=resolved.manifest_main_pipe)
|
|
149
|
+
default_pipe_ref = default_pipe.ref if isinstance(default_pipe, RequestedPipe) else None
|
|
150
|
+
pipe_ref: str | None
|
|
151
|
+
described_pipes: list[PipeAbstract]
|
|
152
|
+
if request_data.all_pipes:
|
|
153
|
+
# A whole-method answer needs no selection: a requested ref is still resolved (and refused
|
|
154
|
+
# when it names nothing), but a method with no single entry pipe is described all the same.
|
|
155
|
+
if request_data.pipe_ref is not None:
|
|
156
|
+
pipe_ref = resolve_requested_pipe(resolved.crate, pipe_ref=request_data.pipe_ref, manifest_main_pipe=resolved.manifest_main_pipe).ref
|
|
157
|
+
else:
|
|
158
|
+
pipe_ref = default_pipe_ref
|
|
159
|
+
# The closure's own pipes in load order, the order `/validate`'s report walks.
|
|
160
|
+
described_pipes = get_own_pipes()
|
|
161
|
+
else:
|
|
162
|
+
requested = resolve_requested_pipe(resolved.crate, pipe_ref=request_data.pipe_ref, manifest_main_pipe=resolved.manifest_main_pipe)
|
|
163
|
+
pipe_ref = requested.ref
|
|
164
|
+
described_pipes = [requested.pipe]
|
|
165
|
+
# Raises `PipeIOContractError` when a pipe's input or output JSON Schema cannot be rendered;
|
|
166
|
+
# it reaches the global handler as a no-verdict 500, exactly as on `/validate`.
|
|
167
|
+
artifacts = build_pipe_io_artifacts(described_pipes)
|
|
168
|
+
pending_signatures = build_pending_signatures(get_pipe_library().get_pipes_dict())
|
|
169
|
+
report = PipeIOValidReport(
|
|
170
|
+
pipe_ref=pipe_ref,
|
|
171
|
+
pipe_io_contracts=artifacts.pipe_io_contracts,
|
|
172
|
+
input_form=artifacts.input_form,
|
|
173
|
+
output_form=artifacts.output_form,
|
|
174
|
+
default_pipe_ref=default_pipe_ref,
|
|
175
|
+
pending_signatures=pending_signatures,
|
|
176
|
+
is_runnable=not pending_signatures,
|
|
177
|
+
)
|
|
178
|
+
# Dumped as `/validate` dumps its valid arm, never with `exclude_none`: that would strip the
|
|
179
|
+
# contracts' `item_count: null` and break the byte equality with `/validate`'s maps.
|
|
180
|
+
content: dict[str, Any] = report.model_dump(mode="json", serialize_as_any=True, by_alias=True, exclude={"files"})
|
|
181
|
+
if request_data.include_files:
|
|
182
|
+
# Each file is echoed in the request's own shape: a `source` the request omitted stays omitted.
|
|
183
|
+
content["files"] = [item.model_dump(mode="json", exclude_none=True) for item in resolved.files]
|
|
184
|
+
return JSONResponse(content=content)
|
|
185
|
+
finally:
|
|
186
|
+
teardown_current_library()
|