arcaflow-plugin-sdk 0.14.0.dev1__tar.gz → 0.14.1__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.
Files changed (17) hide show
  1. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/PKG-INFO +1 -1
  2. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/pyproject.toml +1 -1
  3. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/schema.py +143 -211
  4. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/test_plugin.py +28 -7
  5. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/test_schema.py +210 -0
  6. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/LICENSE +0 -0
  7. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/README.md +0 -0
  8. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/__init__.py +0 -0
  9. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/annotations.py +0 -0
  10. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/atp.py +0 -0
  11. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/jsonschema.py +0 -0
  12. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/plugin.py +0 -0
  13. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/predefined_schemas.py +0 -0
  14. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/serialization.py +0 -0
  15. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/test_atp.py +0 -0
  16. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/test_jsonschema.py +0 -0
  17. {arcaflow_plugin_sdk-0.14.0.dev1 → arcaflow_plugin_sdk-0.14.1}/src/arcaflow_plugin_sdk/validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: arcaflow-plugin-sdk
3
- Version: 0.14.0.dev1
3
+ Version: 0.14.1
4
4
  Summary: Plugin SDK for Python for the Arcaflow workflow engine
5
5
  Home-page: https://github.com/arcalot/arcaflow-plugin-sdk-python
6
6
  License: Apache-2.0
@@ -1,7 +1,7 @@
1
1
  [tool.poetry]
2
2
  name = "arcaflow-plugin-sdk"
3
3
  # Do not change, the version is automatically updated in CI.
4
- version = "v0.14.0-dev1"
4
+ version = "0.14.1"
5
5
  description = "Plugin SDK for Python for the Arcaflow workflow engine"
6
6
  authors = ["Arcalot Contributors"]
7
7
  license = "Apache-2.0"
@@ -2578,7 +2578,114 @@ class ObjectSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2578
2578
 
2579
2579
 
2580
2580
  @dataclass
2581
- class OneOfStringSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2581
+ class OneOfSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2582
+ types: typing.Union[
2583
+ Dict[str, typing.Annotated[_OBJECT_LIKE, discriminator("type_id")]],
2584
+ Dict[int, typing.Annotated[_OBJECT_LIKE, discriminator("type_id")]],
2585
+ ]
2586
+ discriminator_inlined: typing.Annotated[
2587
+ bool,
2588
+ _name("Discriminator field inlined"),
2589
+ _description(
2590
+ "Whether or not the discriminator is inlined in the underlying"
2591
+ " objects' schema"
2592
+ ),
2593
+ ]
2594
+ oneof_type: typing.Annotated[str, _name("One Of Type Schema Name")] = None
2595
+ discriminator_type: typing.Annotated[str, _name("Discriminator Type")] = (
2596
+ None
2597
+ )
2598
+ discriminator_field_name: typing.Annotated[
2599
+ str,
2600
+ _name("Discriminator field name"),
2601
+ _description(
2602
+ "Name of the field used to discriminate between possible values."
2603
+ ),
2604
+ ] = "_type"
2605
+
2606
+ def _insert_discriminator(
2607
+ self,
2608
+ discriminated_object: typing.Dict[str, typing.Any],
2609
+ discriminator_val: str,
2610
+ ) -> typing.Dict[str, typing.Any]:
2611
+ """Add a discriminator field as a property of a member type.
2612
+
2613
+ This function adds a member type's discriminator field as a property
2614
+ with a constant value equal to its discriminated value. The
2615
+ discriminator field is moved to the zeroth index of the list of
2616
+ required fields in a data packet.
2617
+
2618
+ :param discriminated_object: A Python dict which represents the
2619
+ relevant fragment of the scope's JSON definition.
2620
+ :param discriminator_val: The value that represents the given object in
2621
+ its discriminated union.
2622
+ """
2623
+ if self.discriminator_inlined:
2624
+ # update the object's schema to show the only valid value
2625
+ # for this object's discriminator
2626
+ discriminated_object["properties"][
2627
+ self.discriminator_field_name
2628
+ ] = {
2629
+ "type": self.discriminator_type,
2630
+ "const": discriminator_val,
2631
+ }
2632
+ # discriminator field is already present in the required
2633
+ # list when the discriminator is inlined
2634
+ discriminated_object["required"].remove(
2635
+ self.discriminator_field_name
2636
+ )
2637
+ # discriminator must have the first position
2638
+ discriminated_object["required"].insert(
2639
+ 0, self.discriminator_field_name
2640
+ )
2641
+
2642
+ def _to_jsonschema_fragment(
2643
+ self, scope: typing.ForwardRef("ScopeSchema"), defs: _JSONSchemaDefs
2644
+ ) -> any:
2645
+ one_of = []
2646
+ for k, v in self.types.items():
2647
+ # noinspection PyProtectedMember
2648
+ _ = scope.objects[v.id]._to_jsonschema_fragment(scope, defs)
2649
+ self._insert_discriminator(defs.defs[v.id], str(k))
2650
+ if v.display is not None:
2651
+ if v.display.name is not None:
2652
+ defs.defs[v.id]["title"] = v.display.name
2653
+ if v.display.description is not None:
2654
+ defs.defs[v.id]["description"] = v.display.description
2655
+ name = v.id + self.oneof_type + str(k)
2656
+ defs.defs[name] = defs.defs[v.id]
2657
+ one_of.append({"$ref": "#/$defs/" + name})
2658
+ return {"oneOf": one_of}
2659
+
2660
+ def _to_openapi_fragment(
2661
+ self, scope: typing.ForwardRef("ScopeSchema"), defs: _OpenAPIComponents
2662
+ ) -> any:
2663
+ one_of = []
2664
+ discriminator_mapping = {}
2665
+ for k, v in self.types.items():
2666
+ # noinspection PyProtectedMember
2667
+ _ = scope.objects[v.id]._to_openapi_fragment(scope, defs)
2668
+ name = v.id + self.oneof_type + str(k)
2669
+ discriminator_mapping[k] = "#/components/schemas/" + name
2670
+ self._insert_discriminator(defs.defs[v.id], str(k))
2671
+ if v.display is not None:
2672
+ if v.display.name is not None:
2673
+ defs.defs[v.id]["title"] = v.display.name
2674
+ if v.display.description is not None:
2675
+ defs.defs[v.id]["description"] = v.display.description
2676
+ defs.components[name] = defs.defs[v.id]
2677
+ one_of.append({"$ref": "#/components/schemas/" + name})
2678
+ return {
2679
+ "oneOf": one_of,
2680
+ "discriminator": {
2681
+ "propertyName": self.discriminator_field_name,
2682
+ "mapping": discriminator_mapping,
2683
+ },
2684
+ }
2685
+
2686
+
2687
+ @dataclass
2688
+ class OneOfStringSchema(OneOfSchema):
2582
2689
  """This class holds the definition of variable types with a string
2583
2690
  discriminator. This type acts as a split for a case where multiple possible
2584
2691
  object types can be present in a field. This type requires that there be a
@@ -2701,112 +2808,14 @@ class OneOfStringSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2701
2808
  """ # noqa: E501
2702
2809
 
2703
2810
  types: Dict[str, typing.Annotated[_OBJECT_LIKE, discriminator("type_id")]]
2704
- discriminator_inlined: typing.Annotated[
2705
- bool,
2706
- _name("Discriminator field inlined"),
2707
- _description(
2708
- "True if the discriminator is a field in each schema of the"
2709
- " underlying objects"
2710
- ),
2711
- ]
2712
- discriminator_field_name: typing.Annotated[
2713
- str,
2714
- _name("Discriminator field name"),
2715
- _description(
2716
- "Name of the field whose value is used to discriminate between"
2717
- " possible subobject types. If this field is present in any of the"
2718
- " subobjects it must have a type of string."
2719
- ),
2720
- ] = "_type"
2721
2811
 
2722
- def _insert_discriminator(
2723
- self,
2724
- discriminated_object: typing.Dict[str, typing.Any],
2725
- discriminator_val: str,
2726
- ) -> typing.Dict[str, typing.Any]:
2727
- """Add a discriminator field as a property of a member type.
2728
-
2729
- This function adds a member type's discriminator field as a property
2730
- with a constant value equal to its discriminated value. The
2731
- discriminator field is moved to the zeroth index of the list of
2732
- required fields in a data packet.
2733
-
2734
- :param discriminated_object: A Python dict which represents the
2735
- relevant fragment of the scope's JSON definition.
2736
- :param discriminator_val: The value that represents the given object in
2737
- its discriminated union.
2738
- """
2739
- if self.discriminator_inlined:
2740
- # update the object's schema to show the only valid value
2741
- # for this object's discriminator
2742
- discriminated_object["properties"][
2743
- self.discriminator_field_name
2744
- ] = {
2745
- "type": "string",
2746
- "const": discriminator_val,
2747
- }
2748
- # discriminator field is already present in the required
2749
- # list when the discriminator is inlined
2750
- discriminated_object["required"].remove(
2751
- self.discriminator_field_name
2752
- )
2753
- # discriminator must have the first position
2754
- discriminated_object["required"].insert(
2755
- 0, self.discriminator_field_name
2756
- )
2757
-
2758
- def _to_jsonschema_fragment(
2759
- self, scope: typing.ForwardRef("ScopeSchema"), defs: _JSONSchemaDefs
2760
- ) -> any:
2761
- one_of = []
2762
- for k, v in self.types.items():
2763
- # noinspection PyProtectedMember
2764
- scope.objects[v.id]._to_jsonschema_fragment(scope, defs)
2765
-
2766
- self._insert_discriminator(defs.defs[v.id], k)
2767
-
2768
- if v.display is not None:
2769
- if v.display.name is not None:
2770
- defs.defs[v.id]["title"] = v.display.name
2771
- if v.display.description is not None:
2772
- defs.defs[v.id]["description"] = v.display.description
2773
-
2774
- name = v.id + "_discriminated_string_" + _id_typeize(k)
2775
- defs.defs[name] = defs.defs[v.id]
2776
- one_of.append({"$ref": "#/$defs/" + name})
2777
- return {"oneOf": one_of}
2778
-
2779
- def _to_openapi_fragment(
2780
- self, scope: typing.ForwardRef("ScopeSchema"), defs: _OpenAPIComponents
2781
- ) -> any:
2782
- one_of = []
2783
- discriminator_mapping = {}
2784
- for k, v in self.types.items():
2785
- # noinspection PyProtectedMember
2786
- scope.objects[v.id]._to_openapi_fragment(scope, defs)
2787
-
2788
- name = v.id + "_discriminated_string_" + _id_typeize(k)
2789
- discriminator_mapping[k] = "#/components/schemas/" + name
2790
- self._insert_discriminator(defs.defs[v.id], k)
2791
- if v.display is not None:
2792
- if v.display.name is not None:
2793
- defs.defs[v.id]["title"] = v.display.name
2794
- if v.display.description is not None:
2795
- defs.defs[v.id]["description"] = v.display.description
2796
-
2797
- defs.components[name] = defs.defs[v.id]
2798
- one_of.append({"$ref": "#/components/schemas/" + name})
2799
- return {
2800
- "oneOf": one_of,
2801
- "discriminator": {
2802
- "propertyName": self.discriminator_field_name,
2803
- "mapping": discriminator_mapping,
2804
- },
2805
- }
2812
+ def __post_init__(self):
2813
+ self.oneof_type = "_discriminated_string_"
2814
+ self.discriminator_type = "string"
2806
2815
 
2807
2816
 
2808
2817
  @dataclass
2809
- class OneOfIntSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2818
+ class OneOfIntSchema(OneOfSchema):
2810
2819
  """This class holds the definition of variable types with an integer
2811
2820
  discriminator. This type acts as a split for a case where multiple possible
2812
2821
  object types can be present in a field. This type requires that there be a
@@ -2912,106 +2921,10 @@ class OneOfIntSchema(_JSONSchemaGenerator, _OpenAPIGenerator):
2912
2921
  """ # noqa: E501
2913
2922
 
2914
2923
  types: Dict[int, typing.Annotated[_OBJECT_LIKE, discriminator("type_id")]]
2915
- discriminator_inlined: typing.Annotated[
2916
- bool,
2917
- _name("Discriminator field inlined"),
2918
- _description(
2919
- "Whether or not the discriminator is inlined in the underlying"
2920
- " objects' schema"
2921
- ),
2922
- ]
2923
- discriminator_field_name: typing.Annotated[
2924
- str,
2925
- _name("Discriminator field name"),
2926
- _description(
2927
- "Name of the field used to discriminate between possible values."
2928
- " If this field ispresent on any of the component objects it must"
2929
- " also be an int."
2930
- ),
2931
- ] = "_type"
2932
-
2933
- def _insert_discriminator(
2934
- self,
2935
- discriminated_object: typing.Dict[str, typing.Any],
2936
- discriminator_val: str,
2937
- ) -> typing.Dict[str, typing.Any]:
2938
- """Add a discriminator field as a property of a member type.
2939
-
2940
- This function adds a member type's discriminator field as a property
2941
- with a constant value equal to its discriminated value. The
2942
- discriminator field is moved to the zeroth index of the list of
2943
- required fields in a data packet.
2944
-
2945
- :param discriminated_object: A Python dict which represents the
2946
- relevant fragment of the scope's JSON definition.
2947
- :param discriminator_val: The value that represents the given object in
2948
- its discriminated union.
2949
- """
2950
- if self.discriminator_inlined:
2951
- # update the object's schema to show the only valid value
2952
- # for this object's discriminator
2953
- discriminated_object["properties"][
2954
- self.discriminator_field_name
2955
- ] = {
2956
- "type": "string",
2957
- "const": discriminator_val,
2958
- }
2959
- # discriminator field is already present in the required
2960
- # list when the discriminator is inlined
2961
- discriminated_object["required"].remove(
2962
- self.discriminator_field_name
2963
- )
2964
- # discriminator must have the first position
2965
- discriminated_object["required"].insert(
2966
- 0, self.discriminator_field_name
2967
- )
2968
2924
 
2969
- def _to_jsonschema_fragment(
2970
- self, scope: typing.ForwardRef("ScopeSchema"), defs: _JSONSchemaDefs
2971
- ) -> any:
2972
- one_of = []
2973
- for k, v in self.types.items():
2974
- # noinspection PyProtectedMember
2975
- scope.objects[v.id]._to_jsonschema_fragment(scope, defs)
2976
-
2977
- self._insert_discriminator(defs.defs[v.id], str(k))
2978
- if v.display is not None:
2979
- if v.display.name is not None:
2980
- defs.defs[v.id]["title"] = v.display.name
2981
- if v.display.description is not None:
2982
- defs.defs[v.id]["description"] = v.display.description
2983
- name = v.id + "_discriminated_int_" + str(k)
2984
- defs.defs[name] = defs.defs[v.id]
2985
- one_of.append({"$ref": "#/$defs/" + name})
2986
- return {"oneOf": one_of}
2987
-
2988
- def _to_openapi_fragment(
2989
- self, scope: typing.ForwardRef("ScopeSchema"), defs: _OpenAPIComponents
2990
- ) -> any:
2991
- one_of = []
2992
- discriminator_mapping = {}
2993
- for k, v in self.types.items():
2994
- # noinspection PyProtectedMember
2995
- scope.objects[v.id]._to_openapi_fragment(scope, defs)
2996
- name = v.id + "_discriminated_int_" + str(k)
2997
- discriminator_mapping[k] = "#/components/schemas/" + name
2998
-
2999
- self._insert_discriminator(defs.defs[v.id], str(k))
3000
- if v.display is not None:
3001
- if v.display.name is not None:
3002
- defs.defs[v.id]["title"] = v.display.name
3003
- if v.display.description is not None:
3004
- defs.defs[v.id]["description"] = v.display.description
3005
-
3006
- defs.components[name] = defs.defs[v.id]
3007
- one_of.append({"$ref": "#/components/schemas/" + name})
3008
- return {
3009
- "oneOf": one_of,
3010
- "discriminator": {
3011
- "propertyName": self.discriminator_field_name,
3012
- "mapping": discriminator_mapping,
3013
- },
3014
- }
2925
+ def __post_init__(self):
2926
+ self.oneof_type = "_discriminated_int_"
2927
+ self.discriminator_type = "integer"
3015
2928
 
3016
2929
 
3017
2930
  @dataclass
@@ -5621,7 +5534,10 @@ class OneOfStringType(
5621
5534
  ):
5622
5535
  # noinspection PyArgumentList
5623
5536
  OneOfStringSchema.__init__(
5624
- self, types, discriminator_inlined, discriminator_field_name
5537
+ self,
5538
+ types=types,
5539
+ discriminator_inlined=discriminator_inlined,
5540
+ discriminator_field_name=discriminator_field_name,
5625
5541
  )
5626
5542
  _OneOfType.__init__(
5627
5543
  self,
@@ -5659,7 +5575,10 @@ class OneOfIntType(OneOfIntSchema, _OneOfType[OneOfT, int], Generic[OneOfT]):
5659
5575
  ):
5660
5576
  # noinspection PyArgumentList
5661
5577
  OneOfIntSchema.__init__(
5662
- self, types, discriminator_inlined, discriminator_field_name
5578
+ self,
5579
+ types=types,
5580
+ discriminator_inlined=discriminator_inlined,
5581
+ discriminator_field_name=discriminator_field_name,
5663
5582
  )
5664
5583
  _OneOfType.__init__(
5665
5584
  self,
@@ -6070,10 +5989,17 @@ class StepType(StepSchema):
6070
5989
  input.validate(params, tuple(["input"]))
6071
5990
  # Run the step
6072
5991
  result = self._handler(step_local_data.initialized_object, params)
5992
+ if not isinstance(result, tuple):
5993
+ raise BadArgumentException(
5994
+ f"The implementation of step {run_id}/{self.id} returned"
5995
+ f" type {type(result)}; expected a tuple with two"
5996
+ " values: output ID string and a step-specific value."
5997
+ f"\nValue returned: {result}"
5998
+ )
6073
5999
  if len(result) != 2:
6074
6000
  raise BadArgumentException(
6075
- "The step returned {} results instead of 2. Did your step"
6076
- " return the correct results?".format(len(result))
6001
+ f"The implementation of step {run_id}/{self.id} returned"
6002
+ f"{len(result)} results instead of 2. Got {result}."
6077
6003
  )
6078
6004
  output_id, output_data = result
6079
6005
  if output_id not in self.outputs:
@@ -6867,8 +6793,12 @@ class _SchemaBuilder:
6867
6793
  ) -> AbstractType:
6868
6794
  t: typing.ForwardRef
6869
6795
  # TODO is there a better way to directly evaluate a forward ref?
6870
- # noinspection PyArgumentList,PyProtectedMember
6871
- resolved = t._evaluate(None, None, frozenset())
6796
+ # Note: This is an unstable API.
6797
+ # noinspection PyProtectedMember
6798
+ resolved = t._evaluate(
6799
+ globalns=None,
6800
+ localns=None,
6801
+ recursive_guard=frozenset())
6872
6802
  return cls._resolve(resolved, resolved, path, scope)
6873
6803
 
6874
6804
  @classmethod
@@ -7019,12 +6949,14 @@ class _SchemaBuilder:
7019
6949
  types[discriminator_value] = f.type
7020
6950
  if discriminator_type is str:
7021
6951
  return OneOfStringType(
7022
- types,
7023
- scope,
6952
+ types=types,
6953
+ scope=scope,
7024
6954
  discriminator_inlined=False,
7025
6955
  )
7026
6956
  else:
7027
- return OneOfIntType(types, scope, discriminator_inlined=False)
6957
+ return OneOfIntType(
6958
+ types=types, scope=scope, discriminator_inlined=False
6959
+ )
7028
6960
 
7029
6961
  @classmethod
7030
6962
  def _resolve_pattern(cls, t, type_hints: type, path, scope: ScopeType):
@@ -4,16 +4,16 @@ import tempfile
4
4
  import typing
5
5
  import unittest
6
6
 
7
- from arcaflow_plugin_sdk import plugin
7
+ from arcaflow_plugin_sdk import plugin, schema
8
8
 
9
9
 
10
10
  @dataclasses.dataclass
11
- class StdoutTestInput:
11
+ class EmptyTestInput:
12
12
  pass
13
13
 
14
14
 
15
15
  @dataclasses.dataclass
16
- class StdoutTestOutput:
16
+ class EmptyTestOutput:
17
17
  pass
18
18
 
19
19
 
@@ -21,13 +21,13 @@ class StdoutTestOutput:
21
21
  "stdout-test",
22
22
  "Stdout test",
23
23
  "A test for writing to stdout.",
24
- {"success": StdoutTestOutput},
24
+ {"success": EmptyTestOutput},
25
25
  )
26
26
  def stdout_test_step(
27
- input: StdoutTestInput,
28
- ) -> typing.Tuple[str, StdoutTestOutput]:
27
+ _: EmptyTestInput,
28
+ ) -> typing.Tuple[str, EmptyTestOutput]:
29
29
  print("Hello world!")
30
- return "success", StdoutTestOutput()
30
+ return "success", EmptyTestOutput()
31
31
 
32
32
 
33
33
  class StdoutTest(unittest.TestCase):
@@ -52,5 +52,26 @@ class StdoutTest(unittest.TestCase):
52
52
  self.assertEqual("Hello world!\n", e.getvalue())
53
53
 
54
54
 
55
+ @plugin.step(
56
+ "incorrect-return",
57
+ "Incorrect Return",
58
+ "A step that returns a bad output which omits the output ID.",
59
+ {"success": EmptyTestOutput},
60
+ )
61
+ def incorrect_return_step(
62
+ _: EmptyTestInput,
63
+ ) -> typing.Tuple[str, EmptyTestOutput]:
64
+ # noinspection PyTypeChecker
65
+ return EmptyTestOutput()
66
+
67
+
68
+ class CallStepTest(unittest.TestCase):
69
+ def test_incorrect_return_args_count(self):
70
+ s = plugin.build_schema(incorrect_return_step)
71
+
72
+ with self.assertRaises(schema.BadArgumentException):
73
+ s.call_step(self.id(), "incorrect-return", EmptyTestInput())
74
+
75
+
55
76
  if __name__ == "__main__":
56
77
  unittest.main()
@@ -56,6 +56,7 @@ class InlineStr2:
56
56
  class InlineInt:
57
57
  type_: int
58
58
  msg: str
59
+ code: int
59
60
 
60
61
 
61
62
  @dataclasses.dataclass
@@ -81,6 +82,11 @@ class InlineUnion:
81
82
  union: typing.Union[InlineStr, InlineStr2, DoubleInlineStr]
82
83
 
83
84
 
85
+ @dataclasses.dataclass
86
+ class IntInlineUnion:
87
+ union: typing.Union[InlineInt, InlineInt2]
88
+
89
+
84
90
  class Color(enum.Enum):
85
91
  GREEN = "green"
86
92
  RED = "red"
@@ -584,6 +590,30 @@ class OneOfTest(unittest.TestCase):
584
590
  },
585
591
  "a",
586
592
  )
593
+ self.scope_inlined_int = schema.ScopeType(
594
+ {
595
+ "a": schema.ObjectType(
596
+ InlineInt,
597
+ {
598
+ discriminator_field_name: PropertyType(
599
+ schema.IntType(),
600
+ ),
601
+ "msg": PropertyType(schema.StringType()),
602
+ "code": PropertyType(schema.IntType()),
603
+ },
604
+ ),
605
+ "b": schema.ObjectType(
606
+ InlineInt2,
607
+ {
608
+ discriminator_field_name: PropertyType(
609
+ schema.IntType(),
610
+ ),
611
+ "msg2": PropertyType(schema.StringType()),
612
+ },
613
+ ),
614
+ },
615
+ "a",
616
+ )
587
617
 
588
618
  def test_inline_discriminator_missing(self):
589
619
  with self.assertRaises(BadArgumentException) as cm:
@@ -779,6 +809,20 @@ class OneOfTest(unittest.TestCase):
779
809
  self.assertIsInstance(unserialized_data2, DoubleInlineStr)
780
810
  self.assertEqual(unserialized_data2.msg, "Hi again")
781
811
 
812
+ s = schema.OneOfIntType(
813
+ {
814
+ 1: schema.RefType("a", self.scope_inlined_int),
815
+ 2: schema.RefType("b", self.scope_inlined_int),
816
+ },
817
+ scope=self.scope_inlined_int,
818
+ discriminator_field_name=discriminator_field_name,
819
+ discriminator_inlined=True,
820
+ )
821
+ unserialized_data: InlineInt = s.unserialize(
822
+ {discriminator_field_name: 1, "msg": "Hi again", "code": 101}
823
+ )
824
+ self.assertIsInstance(unserialized_data, InlineInt)
825
+
782
826
  def test_validation(self):
783
827
  s = schema.OneOfStringType[Basic](
784
828
  {
@@ -2117,6 +2161,172 @@ class JSONSchemaTest(unittest.TestCase):
2117
2161
  json_schema,
2118
2162
  )
2119
2163
 
2164
+ def test_oneof_int_inline(self):
2165
+ scope = schema.ScopeType(
2166
+ {},
2167
+ IntInlineUnion.__name__,
2168
+ )
2169
+ inline_int_key = 1
2170
+ inline_int2_key = 2
2171
+
2172
+ scope.objects = {
2173
+ IntInlineUnion.__name__: schema.ObjectType(
2174
+ IntInlineUnion,
2175
+ {
2176
+ "union": schema.PropertyType(
2177
+ schema.OneOfIntType(
2178
+ {
2179
+ inline_int_key: schema.RefType(
2180
+ InlineInt.__name__, scope
2181
+ ),
2182
+ inline_int2_key: schema.RefType(
2183
+ InlineInt2.__name__, scope
2184
+ ),
2185
+ },
2186
+ scope,
2187
+ discriminator_inlined=True,
2188
+ discriminator_field_name=discriminator_field_name,
2189
+ )
2190
+ )
2191
+ },
2192
+ ),
2193
+ InlineInt.__name__: schema.ObjectType(
2194
+ InlineInt,
2195
+ {
2196
+ "msg": schema.PropertyType(schema.StringType()),
2197
+ "code": schema.PropertyType(schema.IntType()),
2198
+ discriminator_field_name: schema.PropertyType(
2199
+ schema.IntType()
2200
+ ),
2201
+ },
2202
+ ),
2203
+ InlineInt2.__name__: schema.ObjectType(
2204
+ InlineInt2,
2205
+ {
2206
+ "msg2": schema.PropertyType(schema.StringType()),
2207
+ discriminator_field_name: schema.PropertyType(
2208
+ schema.IntType()
2209
+ ),
2210
+ },
2211
+ ),
2212
+ }
2213
+
2214
+ defs = schema._JSONSchemaDefs()
2215
+ json_schema = scope._to_jsonschema_fragment(scope, defs)
2216
+ self.assertEqual(
2217
+ {
2218
+ "$defs": {
2219
+ IntInlineUnion.__name__: {
2220
+ "type": "object",
2221
+ "properties": {
2222
+ "union": {
2223
+ "oneOf": [
2224
+ {
2225
+ "$ref": (
2226
+ f"#/$defs/{InlineInt.__name__}"
2227
+ "_discriminated_int_"
2228
+ f"{inline_int_key}"
2229
+ )
2230
+ },
2231
+ {
2232
+ "$ref": (
2233
+ f"#/$defs/{InlineInt2.__name__}"
2234
+ "_discriminated_int_"
2235
+ f"{inline_int2_key}"
2236
+ )
2237
+ },
2238
+ ]
2239
+ }
2240
+ },
2241
+ "required": ["union"],
2242
+ "additionalProperties": False,
2243
+ "dependentRequired": {},
2244
+ },
2245
+ InlineInt.__name__: {
2246
+ "type": "object",
2247
+ "properties": {
2248
+ "msg": {"type": "string"},
2249
+ "code": {"type": "integer"},
2250
+ discriminator_field_name: {
2251
+ "type": "integer",
2252
+ "const": str(inline_int_key),
2253
+ },
2254
+ },
2255
+ "required": [discriminator_field_name, "msg", "code"],
2256
+ "additionalProperties": False,
2257
+ "dependentRequired": {},
2258
+ },
2259
+ InlineInt.__name__
2260
+ + f"_discriminated_int_{inline_int_key}": {
2261
+ "type": "object",
2262
+ "properties": {
2263
+ "msg": {"type": "string"},
2264
+ "code": {"type": "integer"},
2265
+ discriminator_field_name: {
2266
+ "type": "integer",
2267
+ "const": str(inline_int_key),
2268
+ },
2269
+ },
2270
+ "required": [discriminator_field_name, "msg", "code"],
2271
+ "additionalProperties": False,
2272
+ "dependentRequired": {},
2273
+ },
2274
+ InlineInt2.__name__: {
2275
+ "type": "object",
2276
+ "properties": {
2277
+ "msg2": {"type": "string"},
2278
+ discriminator_field_name: {
2279
+ "type": "integer",
2280
+ "const": str(inline_int2_key),
2281
+ },
2282
+ },
2283
+ "required": [discriminator_field_name, "msg2"],
2284
+ "additionalProperties": False,
2285
+ "dependentRequired": {},
2286
+ },
2287
+ InlineInt2.__name__
2288
+ + f"_discriminated_int_{inline_int2_key}": {
2289
+ "type": "object",
2290
+ "properties": {
2291
+ "msg2": {"type": "string"},
2292
+ discriminator_field_name: {
2293
+ "type": "integer",
2294
+ "const": str(inline_int2_key),
2295
+ },
2296
+ },
2297
+ "required": [discriminator_field_name, "msg2"],
2298
+ "additionalProperties": False,
2299
+ "dependentRequired": {},
2300
+ },
2301
+ },
2302
+ "type": "object",
2303
+ "properties": {
2304
+ "union": {
2305
+ "oneOf": [
2306
+ {
2307
+ "$ref": (
2308
+ f"#/$defs/{InlineInt.__name__}"
2309
+ "_discriminated_int_"
2310
+ f"{inline_int_key}"
2311
+ )
2312
+ },
2313
+ {
2314
+ "$ref": (
2315
+ f"#/$defs/{InlineInt2.__name__}"
2316
+ "_discriminated_int_"
2317
+ f"{inline_int2_key}"
2318
+ )
2319
+ },
2320
+ ]
2321
+ }
2322
+ },
2323
+ "required": ["union"],
2324
+ "additionalProperties": False,
2325
+ "dependentRequired": {},
2326
+ },
2327
+ json_schema,
2328
+ )
2329
+
2120
2330
 
2121
2331
  def load_tests(loader, tests, ignore):
2122
2332
  """This function adds the doctests to the discovery process."""