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.
Files changed (46) hide show
  1. pipelex_api/__init__.py +0 -0
  2. pipelex_api/api.toml +21 -0
  3. pipelex_api/api_config.py +144 -0
  4. pipelex_api/bundle.py +243 -0
  5. pipelex_api/disclosure.py +43 -0
  6. pipelex_api/error_types.py +80 -0
  7. pipelex_api/error_uri.py +45 -0
  8. pipelex_api/errors.py +130 -0
  9. pipelex_api/exception_handlers.py +693 -0
  10. pipelex_api/json_body.py +182 -0
  11. pipelex_api/limits.py +67 -0
  12. pipelex_api/main.py +221 -0
  13. pipelex_api/method_cache.py +241 -0
  14. pipelex_api/method_source.py +215 -0
  15. pipelex_api/middleware.py +209 -0
  16. pipelex_api/openapi_responses.py +186 -0
  17. pipelex_api/openapi_schema.py +83 -0
  18. pipelex_api/problem_document.py +134 -0
  19. pipelex_api/py.typed +0 -0
  20. pipelex_api/routes/__init__.py +23 -0
  21. pipelex_api/routes/health.py +24 -0
  22. pipelex_api/routes/pipelex/__init__.py +21 -0
  23. pipelex_api/routes/pipelex/agent/__init__.py +11 -0
  24. pipelex_api/routes/pipelex/agent/concept.py +60 -0
  25. pipelex_api/routes/pipelex/agent/models.py +49 -0
  26. pipelex_api/routes/pipelex/agent/pipe_spec.py +59 -0
  27. pipelex_api/routes/pipelex/build/__init__.py +11 -0
  28. pipelex_api/routes/pipelex/build/inputs.py +192 -0
  29. pipelex_api/routes/pipelex/build/output.py +163 -0
  30. pipelex_api/routes/pipelex/build/runner.py +236 -0
  31. pipelex_api/routes/pipelex/codegen.py +164 -0
  32. pipelex_api/routes/pipelex/crate_ops.py +331 -0
  33. pipelex_api/routes/pipelex/pipe_io.py +186 -0
  34. pipelex_api/routes/pipelex/pipeline.py +938 -0
  35. pipelex_api/routes/pipelex/resolve.py +81 -0
  36. pipelex_api/routes/pipelex/tools.py +111 -0
  37. pipelex_api/routes/pipelex/utils.py +6 -0
  38. pipelex_api/routes/pipelex/validate.py +473 -0
  39. pipelex_api/routes/version.py +51 -0
  40. pipelex_api/schemas/__init__.py +0 -0
  41. pipelex_api/schemas/models.py +653 -0
  42. pipelex_api/security.py +284 -0
  43. pipelex_api-0.71.0.dist-info/METADATA +188 -0
  44. pipelex_api-0.71.0.dist-info/RECORD +46 -0
  45. pipelex_api-0.71.0.dist-info/WHEEL +4 -0
  46. 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()