unstructured-platform-plugins 0.0.42__tar.gz → 0.0.45__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.
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/PKG-INFO +5 -1
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/README.md +4 -0
- unstructured_platform_plugins-0.0.45/unstructured_platform_plugins/__version__.py +1 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/etl_uvicorn/api_generator.py +44 -13
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/etl_uvicorn/main.py +21 -0
- unstructured_platform_plugins-0.0.42/unstructured_platform_plugins/__version__.py +0 -1
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/.gitignore +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/LICENSE.md +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/pyproject.toml +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/__init__.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/etl_uvicorn/__init__.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/etl_uvicorn/otel.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/etl_uvicorn/utils.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/__init__.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/filedata_meta.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/json_schema.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/model.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/usage.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/schema/utils.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/type_hints.py +0 -0
- {unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/unstructured_platform_plugins/validate_api.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: unstructured_platform_plugins
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.45
|
|
4
4
|
Summary: Wrapper to convert arbitrary code into a uvicorn/fastapi implementation for Unstructured Platform
|
|
5
5
|
License-File: LICENSE.md
|
|
6
6
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -29,6 +29,10 @@ Description-Content-Type: text/markdown
|
|
|
29
29
|
# Unstructured Platform Plugins
|
|
30
30
|

|
|
31
31
|
|
|
32
|
+
> **Deprecation notice:** `wrap_in_fastapi` (and the `etl-uvicorn` CLI it backs)
|
|
33
|
+
> is deprecated. New plugins should build a FastAPI app directly with explicit
|
|
34
|
+
> handlers for the plugin contract routes (`/invoke`, `/schema`, `/id`, etc.).
|
|
35
|
+
|
|
32
36
|
Information about how to build custom plugins to integrate with Unstructured Platform.
|
|
33
37
|
|
|
34
38
|
## Plugin Development
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Unstructured Platform Plugins
|
|
2
2
|

|
|
3
3
|
|
|
4
|
+
> **Deprecation notice:** `wrap_in_fastapi` (and the `etl-uvicorn` CLI it backs)
|
|
5
|
+
> is deprecated. New plugins should build a FastAPI app directly with explicit
|
|
6
|
+
> handlers for the plugin contract routes (`/invoke`, `/schema`, `/id`, etc.).
|
|
7
|
+
|
|
4
8
|
Information about how to build custom plugins to integrate with Unstructured Platform.
|
|
5
9
|
|
|
6
10
|
## Plugin Development
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.45" # pragma: no cover
|
|
@@ -11,6 +11,7 @@ from fastapi.responses import StreamingResponse
|
|
|
11
11
|
from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor
|
|
12
12
|
from pydantic import BaseModel, Field, create_model
|
|
13
13
|
from starlette.responses import RedirectResponse
|
|
14
|
+
from typing_extensions import deprecated
|
|
14
15
|
from unstructured_ingest.data_types.file_data import BatchFileData, FileData, file_data_from_dict
|
|
15
16
|
from unstructured_ingest.error import UnstructuredIngestError
|
|
16
17
|
from uvicorn.config import LOG_LEVELS
|
|
@@ -108,6 +109,10 @@ def update_filedata_model(new_type) -> BaseModel:
|
|
|
108
109
|
return new_filedata_model
|
|
109
110
|
|
|
110
111
|
|
|
112
|
+
@deprecated(
|
|
113
|
+
"wrap_in_fastapi is deprecated; build a FastAPI app directly with "
|
|
114
|
+
"explicit handlers for the plugin contract routes."
|
|
115
|
+
)
|
|
111
116
|
def wrap_in_fastapi(
|
|
112
117
|
func: Callable,
|
|
113
118
|
plugin_id: str,
|
|
@@ -249,22 +254,48 @@ def _wrap_in_fastapi(
|
|
|
249
254
|
file_data=request_dict.get("file_data", None),
|
|
250
255
|
)
|
|
251
256
|
|
|
252
|
-
|
|
257
|
+
async def run_job_with_body(request: BaseModel) -> ResponseType:
|
|
258
|
+
log_func_and_body(func=func, body=request.json())
|
|
259
|
+
# Create dictionary from pydantic model while preserving underlying types
|
|
260
|
+
request_dict = {f: getattr(request, f) for f in request.model_fields}
|
|
261
|
+
# Make sure nested classes get instantiated correctly. `file_data` can legitimately be None
|
|
262
|
+
# -- a plugin may declare it optional, and then an absent or partial body leaves it unset --
|
|
263
|
+
# so convert only a real value. Calling `.model_dump()` on None would raise before `wrap_fn`
|
|
264
|
+
# runs, turning the optional-body contract into a 500.
|
|
265
|
+
file_data = request_dict.get("file_data")
|
|
266
|
+
if file_data is not None:
|
|
267
|
+
request_dict["file_data"] = file_data_from_dict(file_data.model_dump())
|
|
268
|
+
map_inputs(func=func, raw_inputs=request_dict)
|
|
269
|
+
if logger.level == LOG_LEVELS.get("trace", logging.NOTSET):
|
|
270
|
+
logger.log(level=logger.level, msg=f"passing inputs to function: {request_dict}")
|
|
271
|
+
return await wrap_fn(func=func, kwargs=request_dict)
|
|
272
|
+
|
|
273
|
+
# A pydantic body parameter with no default is mandatory even when every field inside the model
|
|
274
|
+
# is optional. So a plugin whose parameters are ALL optional would demand a body that no caller
|
|
275
|
+
# has a reason to populate -- and before it grew those parameters that same plugin accepted no
|
|
276
|
+
# body at all, so adding one flips the HTTP contract while looking backward-compatible. Default
|
|
277
|
+
# the body in that case: an absent body resolves each field to its own default, which is exactly
|
|
278
|
+
# what the function signature already promises.
|
|
279
|
+
#
|
|
280
|
+
# A plugin with at least one required field keeps a mandatory body, so an indexer that needs
|
|
281
|
+
# `file_data` still fails validation rather than silently receiving None.
|
|
282
|
+
body_is_optional = input_schema_model.model_fields and not any(
|
|
283
|
+
field.is_required() for field in input_schema_model.model_fields.values()
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
if body_is_optional:
|
|
287
|
+
|
|
288
|
+
@fastapi_app.post("/invoke", response_model=InvokeResponse)
|
|
289
|
+
async def run_job(request: Optional[input_schema_model] = None) -> ResponseType:
|
|
290
|
+
return await run_job_with_body(
|
|
291
|
+
request if request is not None else input_schema_model()
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
elif input_schema_model.model_fields:
|
|
253
295
|
|
|
254
296
|
@fastapi_app.post("/invoke", response_model=InvokeResponse)
|
|
255
297
|
async def run_job(request: input_schema_model) -> ResponseType:
|
|
256
|
-
|
|
257
|
-
# Create dictionary from pydantic model while preserving underlying types
|
|
258
|
-
request_dict = {f: getattr(request, f) for f in request.model_fields}
|
|
259
|
-
# Make sure nested classes get instantiated correctly
|
|
260
|
-
if "file_data" in request_dict:
|
|
261
|
-
request_dict["file_data"] = file_data_from_dict(
|
|
262
|
-
request_dict["file_data"].model_dump()
|
|
263
|
-
)
|
|
264
|
-
map_inputs(func=func, raw_inputs=request_dict)
|
|
265
|
-
if logger.level == LOG_LEVELS.get("trace", logging.NOTSET):
|
|
266
|
-
logger.log(level=logger.level, msg=f"passing inputs to function: {request_dict}")
|
|
267
|
-
return await wrap_fn(func=func, kwargs=request_dict)
|
|
298
|
+
return await run_job_with_body(request)
|
|
268
299
|
|
|
269
300
|
else:
|
|
270
301
|
|
|
@@ -1,13 +1,34 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import signal
|
|
3
|
+
import threading
|
|
1
4
|
from dataclasses import dataclass, field
|
|
2
5
|
from typing import IO, Any, Optional
|
|
3
6
|
|
|
4
7
|
import click
|
|
8
|
+
import uvicorn
|
|
5
9
|
from uvicorn.config import LOGGING_CONFIG, Config, RawConfigParser
|
|
6
10
|
from uvicorn.main import main, run
|
|
7
11
|
|
|
8
12
|
from unstructured_platform_plugins.etl_uvicorn.api_generator import generate_fast_api
|
|
9
13
|
|
|
10
14
|
|
|
15
|
+
def _install_signal_handlers_ignoring_sigterm(self: uvicorn.Server) -> None:
|
|
16
|
+
# uvicorn's default load-sheds 504 on SIGTERM, which races with controllers
|
|
17
|
+
# trying to drain in-flight work during pod shutdown. Plugin webservers are
|
|
18
|
+
# expected to outlive their controller container so SIGKILL — not SIGTERM —
|
|
19
|
+
# ends the process. SIGINT is preserved so local Ctrl-C still works.
|
|
20
|
+
if threading.current_thread() is not threading.main_thread():
|
|
21
|
+
return
|
|
22
|
+
try:
|
|
23
|
+
loop = asyncio.get_event_loop()
|
|
24
|
+
loop.add_signal_handler(signal.SIGINT, self.handle_exit, signal.SIGINT, None)
|
|
25
|
+
except NotImplementedError:
|
|
26
|
+
signal.signal(signal.SIGINT, self.handle_exit)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
uvicorn.Server.install_signal_handlers = _install_signal_handlers_ignoring_sigterm
|
|
30
|
+
|
|
31
|
+
|
|
11
32
|
@dataclass
|
|
12
33
|
class CustomConfig:
|
|
13
34
|
log_config: dict[str, Any] | str | RawConfigParser | IO[Any] | None = field(
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.42" # pragma: no cover
|
|
File without changes
|
|
File without changes
|
{unstructured_platform_plugins-0.0.42 → unstructured_platform_plugins-0.0.45}/pyproject.toml
RENAMED
|
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
|