unstructured-platform-plugins 0.0.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 (34) hide show
  1. test/__init__.py +0 -0
  2. test/assets/__init__.py +0 -0
  3. test/assets/async_typed_dict_response.py +10 -0
  4. test/assets/dataclass_response.py +37 -0
  5. test/assets/empty_input_and_response.py +8 -0
  6. test/assets/hash_function.py +2 -0
  7. test/assets/improper_function.py +2 -0
  8. test/assets/pydantic_response_class_method.py +21 -0
  9. test/assets/simple_hash_class.py +6 -0
  10. test/assets/simple_hash_lambda.py +1 -0
  11. test/assets/simple_hash_value.py +1 -0
  12. test/assets/typed_dict_response.py +17 -0
  13. test/test_schema.py +660 -0
  14. test/test_utils.py +140 -0
  15. unstructured_platform_plugins/__init__.py +0 -0
  16. unstructured_platform_plugins/__version__.py +1 -0
  17. unstructured_platform_plugins/etl_uvicorn/__init__.py +0 -0
  18. unstructured_platform_plugins/etl_uvicorn/api_generator.py +173 -0
  19. unstructured_platform_plugins/etl_uvicorn/main.py +121 -0
  20. unstructured_platform_plugins/etl_uvicorn/utils.py +115 -0
  21. unstructured_platform_plugins/schema/__init__.py +0 -0
  22. unstructured_platform_plugins/schema/json_schema.py +344 -0
  23. unstructured_platform_plugins/schema/model.py +101 -0
  24. unstructured_platform_plugins/schema/usage.py +7 -0
  25. unstructured_platform_plugins/schema/utils.py +31 -0
  26. unstructured_platform_plugins/type_hints.py +106 -0
  27. unstructured_platform_plugins/validate_api.py +110 -0
  28. unstructured_platform_plugins-0.0.0.dist-info/LICENSE +201 -0
  29. unstructured_platform_plugins-0.0.0.dist-info/LICENSE.md +201 -0
  30. unstructured_platform_plugins-0.0.0.dist-info/METADATA +106 -0
  31. unstructured_platform_plugins-0.0.0.dist-info/RECORD +34 -0
  32. unstructured_platform_plugins-0.0.0.dist-info/WHEEL +5 -0
  33. unstructured_platform_plugins-0.0.0.dist-info/entry_points.txt +3 -0
  34. unstructured_platform_plugins-0.0.0.dist-info/top_level.txt +2 -0
test/test_schema.py ADDED
@@ -0,0 +1,660 @@
1
+ import inspect
2
+ from dataclasses import dataclass
3
+ from enum import Enum
4
+ from pathlib import Path
5
+ from typing import Any, Optional, Union
6
+
7
+ import pytest
8
+ from pydantic import BaseModel
9
+ from typing_extensions import TypedDict
10
+ from unstructured.ingest.v2.interfaces import FileData
11
+
12
+ import unstructured_platform_plugins.schema.json_schema as js
13
+ from unstructured_platform_plugins.etl_uvicorn.utils import get_input_schema
14
+ from unstructured_platform_plugins.schema.model import is_valid_input_dict, is_valid_response_dict
15
+ from unstructured_platform_plugins.schema.utils import get_typed_parameters
16
+ from unstructured_platform_plugins.type_hints import get_type_hints
17
+
18
+
19
+ def test_string_enum_fn():
20
+ class StringEnum(Enum):
21
+ FIRST = "first"
22
+ SECOND = "second"
23
+ THIRD = "third"
24
+
25
+ def fn(input: StringEnum) -> None:
26
+ pass
27
+
28
+ sig = inspect.signature(fn)
29
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
30
+ expected_schema = {
31
+ "type": "object",
32
+ "required": ["input"],
33
+ "properties": {"input": {"type": "string", "enum": ["first", "second", "third"]}},
34
+ }
35
+
36
+ assert input_schema == expected_schema
37
+ assert is_valid_response_dict(input_schema)
38
+
39
+
40
+ def test_int_enum_fn():
41
+ class IntEnum(Enum):
42
+ FIRST = 1
43
+ SECOND = 2
44
+ THIRD = 3
45
+
46
+ def fn(input: IntEnum) -> None:
47
+ pass
48
+
49
+ sig = inspect.signature(fn)
50
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
51
+ expected_schema = {
52
+ "type": "object",
53
+ "required": ["input"],
54
+ "properties": {"input": {"type": "integer", "enum": [1, 2, 3]}},
55
+ }
56
+ assert input_schema == expected_schema
57
+ assert is_valid_response_dict(input_schema)
58
+
59
+
60
+ def test_mixed_enum_fn():
61
+ class MixedEnum(Enum):
62
+ FIRST = 1
63
+ SECOND = "second"
64
+ THIRD = 3
65
+
66
+ def fn(input: MixedEnum) -> None:
67
+ pass
68
+
69
+ sig = inspect.signature(fn)
70
+ with pytest.raises(ValueError):
71
+ js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
72
+
73
+
74
+ def test_blank_fn():
75
+ def fn() -> None:
76
+ pass
77
+
78
+ sig = inspect.signature(fn)
79
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
80
+ expected_schema = {"type": "null"}
81
+ assert input_schema == expected_schema
82
+ assert is_valid_input_dict(input_schema)
83
+
84
+ output_schema = js.response_to_json_schema(return_annotation=sig.return_annotation)
85
+ expected_output_schema = {"type": "null"}
86
+ assert output_schema == expected_output_schema
87
+ assert is_valid_response_dict(output_schema)
88
+
89
+
90
+ def test_simple_fn():
91
+ class Response(TypedDict):
92
+ x: int
93
+ y: str
94
+
95
+ def fn(
96
+ a: int,
97
+ b: Union[float, int],
98
+ c: Optional[str] = "my_string",
99
+ d: bool = False,
100
+ e: dict[str, dict[str, bool]] = None,
101
+ f: list[float] = None,
102
+ ) -> Response:
103
+ return Response(x=1, y="y")
104
+
105
+ sig = inspect.signature(fn)
106
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
107
+ expected_input_schema = {
108
+ "type": "object",
109
+ "required": ["a", "b"],
110
+ "properties": {
111
+ "a": {"type": "integer"},
112
+ "b": {"anyOf": [{"type": "number"}, {"type": "integer"}]},
113
+ "c": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": "my_string"},
114
+ "d": {"type": "boolean", "default": False},
115
+ "e": {
116
+ "type": "object",
117
+ "items": {
118
+ "key": {"type": "string"},
119
+ "value": {
120
+ "type": "object",
121
+ "items": {"key": {"type": "string"}, "value": {"type": "boolean"}},
122
+ },
123
+ },
124
+ "default": None,
125
+ },
126
+ "f": {"type": "array", "items": {"type": "number"}, "default": None},
127
+ },
128
+ }
129
+ assert input_schema == expected_input_schema
130
+ assert is_valid_input_dict(input_schema)
131
+
132
+ output_schema = js.response_to_json_schema(return_annotation=sig.return_annotation)
133
+ expected_output_schema = {
134
+ "type": "object",
135
+ "properties": {"x": {"type": "integer"}, "y": {"type": "string"}},
136
+ "required": ["x", "y"],
137
+ }
138
+ assert output_schema == expected_output_schema
139
+ assert is_valid_response_dict(output_schema)
140
+
141
+
142
+ def test_incorrect_dict_response():
143
+ def fn() -> dict[str, Any]:
144
+ pass
145
+
146
+ sig = inspect.signature(fn)
147
+ with pytest.raises(ValueError):
148
+ js.response_to_json_schema(return_annotation=sig.return_annotation)
149
+
150
+
151
+ def test_incorrect_int_response():
152
+ def fn() -> int:
153
+ pass
154
+
155
+ sig = inspect.signature(fn)
156
+ with pytest.raises(ValueError):
157
+ js.response_to_json_schema(return_annotation=sig.return_annotation)
158
+
159
+
160
+ def test_incorrect_inputs():
161
+ def fn(*args, **kwargs) -> None:
162
+ pass
163
+
164
+ sig = inspect.signature(fn)
165
+ with pytest.raises(TypeError):
166
+ js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
167
+
168
+
169
+ def test_incorrect_union_response():
170
+ class CorrectResponse(TypedDict):
171
+ x: int
172
+
173
+ def fn() -> Union[CorrectResponse, float]:
174
+ pass
175
+
176
+ sig = inspect.signature(fn)
177
+ with pytest.raises(ValueError):
178
+ js.response_to_json_schema(return_annotation=sig.return_annotation)
179
+
180
+
181
+ def test_dataclass_schema():
182
+ @dataclass
183
+ class Input:
184
+ x: int
185
+ y: Optional[str] = None
186
+
187
+ @dataclass
188
+ class Output:
189
+ a: dict[str, Any]
190
+ b: list[bool]
191
+ c: Optional[float] = 5.46
192
+
193
+ def fn(g: Input) -> Optional[Output]:
194
+ pass
195
+
196
+ sig = inspect.signature(fn)
197
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
198
+ expected_input_schema = {
199
+ "type": "object",
200
+ "required": ["g"],
201
+ "properties": {
202
+ "g": {
203
+ "type": "object",
204
+ "properties": {
205
+ "x": {"type": "integer"},
206
+ "y": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": None},
207
+ },
208
+ "required": ["x"],
209
+ }
210
+ },
211
+ }
212
+ assert input_schema == expected_input_schema
213
+ assert is_valid_input_dict(input_schema)
214
+
215
+ output_schema = js.response_to_json_schema(return_annotation=sig.return_annotation)
216
+ expected_output_schema = {
217
+ "anyOf": [
218
+ {
219
+ "type": "object",
220
+ "properties": {
221
+ "a": {"type": "object", "items": {"key": {"type": "string"}, "value": {}}},
222
+ "b": {"type": "array", "items": {"type": "boolean"}},
223
+ "c": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": 5.46},
224
+ },
225
+ "required": ["a", "b"],
226
+ },
227
+ {"type": "null"},
228
+ ]
229
+ }
230
+ assert output_schema == expected_output_schema
231
+ assert is_valid_response_dict(output_schema)
232
+
233
+
234
+ def test_pydantic_base_model():
235
+ class Input(BaseModel):
236
+ x: "int"
237
+ y: Optional[str] = None
238
+
239
+ class Output(BaseModel):
240
+ a: dict[str, Any]
241
+ b: list[bool]
242
+ c: Optional[float] = 5.46
243
+
244
+ def fn(g: Input) -> Optional[Output]:
245
+ pass
246
+
247
+ sig = inspect.signature(fn)
248
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
249
+ expected_input_schema = {
250
+ "type": "object",
251
+ "required": ["g"],
252
+ "properties": {
253
+ "g": {
254
+ "type": "object",
255
+ "properties": {
256
+ "x": {"type": "integer"},
257
+ "y": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": None},
258
+ },
259
+ "required": ["x"],
260
+ }
261
+ },
262
+ }
263
+ assert input_schema == expected_input_schema
264
+ assert is_valid_input_dict(input_schema)
265
+
266
+ output_schema = js.response_to_json_schema(return_annotation=sig.return_annotation)
267
+ expected_output_schema = {
268
+ "anyOf": [
269
+ {
270
+ "type": "object",
271
+ "properties": {
272
+ "a": {"type": "object", "items": {"key": {"type": "string"}, "value": {}}},
273
+ "b": {"type": "array", "items": {"type": "boolean"}},
274
+ "c": {"anyOf": [{"type": "number"}, {"type": "null"}], "default": 5.46},
275
+ },
276
+ "required": ["a", "b"],
277
+ },
278
+ {"type": "null"},
279
+ ]
280
+ }
281
+ assert output_schema == expected_output_schema
282
+ assert is_valid_response_dict(output_schema)
283
+
284
+
285
+ def test_typed_dict():
286
+ class Input(TypedDict):
287
+ x: "int"
288
+ y: Optional[str]
289
+
290
+ class Output(TypedDict):
291
+ a: dict[str, Any]
292
+ b: list[bool]
293
+ c: Optional[float]
294
+
295
+ def fn(g: Input) -> Optional[Output]:
296
+ pass
297
+
298
+ sig = inspect.signature(fn)
299
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
300
+ expected_input_schema = {
301
+ "type": "object",
302
+ "required": ["g"],
303
+ "properties": {
304
+ "g": {
305
+ "type": "object",
306
+ "properties": {
307
+ "x": {"type": "integer"},
308
+ "y": {"anyOf": [{"type": "string"}, {"type": "null"}]},
309
+ },
310
+ "required": ["x", "y"],
311
+ }
312
+ },
313
+ }
314
+ assert input_schema == expected_input_schema
315
+ assert is_valid_input_dict(input_schema)
316
+
317
+ output_schema = js.response_to_json_schema(return_annotation=sig.return_annotation)
318
+ expected_output_schema = {
319
+ "anyOf": [
320
+ {
321
+ "type": "object",
322
+ "properties": {
323
+ "a": {"type": "object", "items": {"key": {"type": "string"}, "value": {}}},
324
+ "b": {"type": "array", "items": {"type": "boolean"}},
325
+ "c": {"anyOf": [{"type": "number"}, {"type": "null"}]},
326
+ },
327
+ "required": ["a", "b", "c"],
328
+ },
329
+ {"type": "null"},
330
+ ]
331
+ }
332
+ assert output_schema == expected_output_schema
333
+ assert is_valid_response_dict(output_schema)
334
+
335
+
336
+ def test_nested_complex_types():
337
+ class InputA(TypedDict):
338
+ x: int
339
+ y: Optional[str]
340
+
341
+ class InputB(BaseModel):
342
+ a: float
343
+ b: Optional[InputA] = None
344
+
345
+ @dataclass
346
+ class InputC:
347
+ g: list[InputB]
348
+ h: bool = False
349
+
350
+ def fn(q: InputC) -> None:
351
+ pass
352
+
353
+ sig = inspect.signature(fn)
354
+ input_schema = js.parameters_to_json_schema(parameters=list(sig.parameters.values()))
355
+ expected_input_schema = {
356
+ "type": "object",
357
+ "required": ["q"],
358
+ "properties": {
359
+ "q": {
360
+ "type": "object",
361
+ "properties": {
362
+ "g": {
363
+ "type": "array",
364
+ "items": {
365
+ "type": "object",
366
+ "properties": {
367
+ "a": {"type": "number"},
368
+ "b": {
369
+ "anyOf": [
370
+ {
371
+ "type": "object",
372
+ "properties": {
373
+ "x": {"type": "integer"},
374
+ "y": {
375
+ "anyOf": [{"type": "string"}, {"type": "null"}]
376
+ },
377
+ },
378
+ "required": ["x", "y"],
379
+ },
380
+ {"type": "null"},
381
+ ],
382
+ "default": None,
383
+ },
384
+ },
385
+ "required": ["a"],
386
+ },
387
+ },
388
+ "h": {"type": "boolean", "default": False},
389
+ },
390
+ "required": ["g"],
391
+ }
392
+ },
393
+ }
394
+ assert input_schema == expected_input_schema
395
+ assert is_valid_input_dict(input_schema)
396
+
397
+
398
+ def test_schema_to_base_model():
399
+ class g_enum(Enum):
400
+ FIRST = "first"
401
+ SECOND = "second"
402
+ THIRD = "third"
403
+
404
+ def fn(
405
+ a: int,
406
+ b: float | int = 4,
407
+ c: str | None = "my_string",
408
+ d: bool = False,
409
+ e: Optional[dict[str, Any]] = None,
410
+ f: list[float] = None,
411
+ g: Optional[g_enum] = None,
412
+ h: FileData | None = None,
413
+ i: Path | None = None,
414
+ ) -> None:
415
+ pass
416
+
417
+ class ExpectedInputModel(BaseModel):
418
+ a: int
419
+ b: Union[float, int] = 4
420
+ c: str | None = "my_string"
421
+ d: bool = False
422
+ e: Optional[dict[str, Any]] = None
423
+ f: list[float] = None
424
+ g: Optional[g_enum] = None
425
+ h: FileData | None = None
426
+ i: Path | None = None
427
+
428
+ input_schema = get_input_schema(fn)
429
+ input_model = js.schema_to_base_model(schema=input_schema)
430
+ input_model_schema = input_model.model_json_schema()
431
+ expected_model_schema = ExpectedInputModel.model_json_schema()
432
+ expected_model_schema["title"] = "reconstructed_model"
433
+ assert input_model_schema == expected_model_schema
434
+
435
+
436
+ # These need to be defined outside the code of test_forward_reference_typing
437
+ # for references to resolve:
438
+ @dataclass
439
+ class MockInputClass:
440
+ a: str
441
+
442
+
443
+ @dataclass
444
+ class MockOutputClass:
445
+ b: bool
446
+
447
+
448
+ def test_forward_reference_typing():
449
+
450
+ def fn(a: "MockInputClass") -> "MockOutputClass":
451
+ pass
452
+
453
+ parameters = get_typed_parameters(fn)
454
+ input_schema = js.parameters_to_json_schema(parameters=parameters)
455
+ expected_input_schema = {
456
+ "type": "object",
457
+ "required": ["a"],
458
+ "properties": {
459
+ "a": {"type": "object", "properties": {"a": {"type": "string"}}, "required": ["a"]}
460
+ },
461
+ }
462
+ assert input_schema == expected_input_schema
463
+ assert is_valid_input_dict(input_schema)
464
+
465
+ return_annotation = get_type_hints(fn)["return"]
466
+ output_schema = js.response_to_json_schema(return_annotation=return_annotation)
467
+ expected_output_schema = {
468
+ "type": "object",
469
+ "properties": {"b": {"type": "boolean"}},
470
+ "required": ["b"],
471
+ }
472
+ assert output_schema == expected_output_schema
473
+ assert is_valid_response_dict(output_schema)
474
+
475
+
476
+ def test_file_data():
477
+ def fn(a: FileData) -> list[FileData]:
478
+ pass
479
+
480
+ parameters = get_typed_parameters(fn)
481
+ input_schema = js.parameters_to_json_schema(parameters=parameters)
482
+ expected_input_schema = {
483
+ "type": "object",
484
+ "required": ["a"],
485
+ "properties": {
486
+ "a": {
487
+ "type": "object",
488
+ "is_file_data": True,
489
+ "properties": {
490
+ "identifier": {"type": "string"},
491
+ "connector_type": {"type": "string"},
492
+ "source_identifiers": {
493
+ "anyOf": [
494
+ {
495
+ "type": "object",
496
+ "properties": {
497
+ "filename": {"type": "string"},
498
+ "fullpath": {"type": "string"},
499
+ "rel_path": {
500
+ "anyOf": [{"type": "string"}, {"type": "null"}],
501
+ "default": None,
502
+ },
503
+ },
504
+ "required": ["filename", "fullpath"],
505
+ },
506
+ {"type": "null"},
507
+ ],
508
+ "default": None,
509
+ },
510
+ "doc_type": {"type": "string", "enum": ["batch", "file"], "default": "file"},
511
+ "metadata": {
512
+ "type": "object",
513
+ "properties": {
514
+ "url": {
515
+ "anyOf": [{"type": "string"}, {"type": "null"}],
516
+ "default": None,
517
+ },
518
+ "version": {
519
+ "anyOf": [{"type": "string"}, {"type": "null"}],
520
+ "default": None,
521
+ },
522
+ "record_locator": {
523
+ "anyOf": [
524
+ {
525
+ "type": "object",
526
+ "items": {"key": {"type": "string"}, "value": {}},
527
+ },
528
+ {"type": "null"},
529
+ ],
530
+ "default": None,
531
+ },
532
+ "date_created": {
533
+ "anyOf": [{"type": "string"}, {"type": "null"}],
534
+ "default": None,
535
+ },
536
+ "date_modified": {
537
+ "anyOf": [{"type": "string"}, {"type": "null"}],
538
+ "default": None,
539
+ },
540
+ "date_processed": {
541
+ "anyOf": [{"type": "string"}, {"type": "null"}],
542
+ "default": None,
543
+ },
544
+ "permissions_data": {
545
+ "anyOf": [
546
+ {
547
+ "type": "array",
548
+ "items": {
549
+ "type": "object",
550
+ "items": {"key": {"type": "string"}, "value": {}},
551
+ },
552
+ },
553
+ {"type": "null"},
554
+ ],
555
+ "default": None,
556
+ },
557
+ },
558
+ "required": [],
559
+ },
560
+ "additional_metadata": {
561
+ "type": "object",
562
+ "items": {"key": {"type": "string"}, "value": {}},
563
+ },
564
+ "reprocess": {"type": "boolean", "default": False},
565
+ },
566
+ "required": ["identifier", "connector_type", "metadata", "additional_metadata"],
567
+ }
568
+ },
569
+ }
570
+
571
+ assert input_schema == expected_input_schema
572
+ assert is_valid_input_dict(input_schema)
573
+
574
+ return_annotation = get_type_hints(fn)["return"]
575
+ output_schema = js.response_to_json_schema(return_annotation=return_annotation)
576
+ expected_output_schema = {
577
+ "type": "array",
578
+ "items": {
579
+ "type": "object",
580
+ "is_file_data": True,
581
+ "properties": {
582
+ "identifier": {"type": "string"},
583
+ "connector_type": {"type": "string"},
584
+ "source_identifiers": {
585
+ "anyOf": [
586
+ {
587
+ "type": "object",
588
+ "properties": {
589
+ "filename": {"type": "string"},
590
+ "fullpath": {"type": "string"},
591
+ "rel_path": {
592
+ "anyOf": [{"type": "string"}, {"type": "null"}],
593
+ "default": None,
594
+ },
595
+ },
596
+ "required": ["filename", "fullpath"],
597
+ },
598
+ {"type": "null"},
599
+ ],
600
+ "default": None,
601
+ },
602
+ "doc_type": {"type": "string", "enum": ["batch", "file"], "default": "file"},
603
+ "metadata": {
604
+ "type": "object",
605
+ "properties": {
606
+ "url": {"anyOf": [{"type": "string"}, {"type": "null"}], "default": None},
607
+ "version": {
608
+ "anyOf": [{"type": "string"}, {"type": "null"}],
609
+ "default": None,
610
+ },
611
+ "record_locator": {
612
+ "anyOf": [
613
+ {
614
+ "type": "object",
615
+ "items": {"key": {"type": "string"}, "value": {}},
616
+ },
617
+ {"type": "null"},
618
+ ],
619
+ "default": None,
620
+ },
621
+ "date_created": {
622
+ "anyOf": [{"type": "string"}, {"type": "null"}],
623
+ "default": None,
624
+ },
625
+ "date_modified": {
626
+ "anyOf": [{"type": "string"}, {"type": "null"}],
627
+ "default": None,
628
+ },
629
+ "date_processed": {
630
+ "anyOf": [{"type": "string"}, {"type": "null"}],
631
+ "default": None,
632
+ },
633
+ "permissions_data": {
634
+ "anyOf": [
635
+ {
636
+ "type": "array",
637
+ "items": {
638
+ "type": "object",
639
+ "items": {"key": {"type": "string"}, "value": {}},
640
+ },
641
+ },
642
+ {"type": "null"},
643
+ ],
644
+ "default": None,
645
+ },
646
+ },
647
+ "required": [],
648
+ },
649
+ "additional_metadata": {
650
+ "type": "object",
651
+ "items": {"key": {"type": "string"}, "value": {}},
652
+ },
653
+ "reprocess": {"type": "boolean", "default": False},
654
+ },
655
+ "required": ["identifier", "connector_type", "metadata", "additional_metadata"],
656
+ },
657
+ }
658
+
659
+ assert output_schema == expected_output_schema
660
+ assert is_valid_response_dict(output_schema)