From 1af11fa66be9ca287017e6e19536e33b2b967bb2 Mon Sep 17 00:00:00 2001 From: ghandic Date: Mon, 13 May 2024 22:42:29 +1000 Subject: [PATCH 1/3] Fix: Minimum required props using oneOf --- jsf/parser.py | 4 ++-- jsf/schema_types/base.py | 19 +++++++++++++++++++ jsf/schema_types/enum.py | 2 +- jsf/tests/data/bool-enum.json | 3 +++ jsf/tests/data/min-required-props.json | 15 +++++++++++++++ jsf/tests/test_default_fake.py | 23 +++++++++++++++++++++++ 6 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 jsf/tests/data/bool-enum.json create mode 100644 jsf/tests/data/min-required-props.json diff --git a/jsf/parser.py b/jsf/parser.py index 3174429..92f5e90 100644 --- a/jsf/parser.py +++ b/jsf/parser.py @@ -264,8 +264,8 @@ def __parse_definition( enum_list = schema["enum"] assert len(enum_list) > 0, "Enum List is Empty" assert all( - isinstance(item, (int, float, str, dict, type(None))) for item in enum_list - ), "Enum Type is not null, int, float, string or dict" + isinstance(item, (bool, int, float, str, dict, type(None))) for item in enum_list + ), "Enum Type is not null, bool, int, float, string or dict" return JSFEnum.from_dict( { "name": name, diff --git a/jsf/schema_types/base.py b/jsf/schema_types/base.py index 3edd443..50d8a92 100644 --- a/jsf/schema_types/base.py +++ b/jsf/schema_types/base.py @@ -1,3 +1,4 @@ +from collections import ChainMap import logging import random import uuid @@ -27,6 +28,10 @@ class BaseSchema(BaseModel): schema_: Optional[str] = Field(None, alias="$schema") # The $comment keyword is strictly intended for adding comments to the JSON schema source. Its value must always be a string. Unlike the annotations title, description and examples, JSON schema implementations aren't allowed to attach any meaning or behavior to it whatsoever, and may even strip them at any time. Therefore, they are useful for leaving notes to future editors of a JSON schema, (which is quite likely your future self), but should not be used to communicate to users of the schema. comments: Optional[str] = Field(None, alias="$comments") + + oneOf: Optional[List[Dict[str, Any]]] = None + anyOf: Optional[List[Dict[str, Any]]] = None + allOf: Optional[List[Dict[str, Any]]] = None # JSF Custom fields path: Optional[str] = None @@ -49,6 +54,20 @@ def generate(self, context: Dict[str, Any]) -> Any: if self.set_state is not None: context["state"][self.path] = {k: eval(v, context)() for k, v in self.set_state.items()} + if self.oneOf is not None and self.__class__.__name__ != "OneOf": + additional_validations = random.choice(self.oneOf) + for k, v in additional_validations.items(): + setattr(self, k, v) + + if self.allOf is not None and self.__class__.__name__ != "AllOf": + additional_validations = dict(ChainMap(*self.allOf)) + for k, v in additional_validations.items(): + setattr(self, k, v) + if self.anyOf is not None and self.__class__.__name__ != "AnyOf": + additional_validations = dict(ChainMap(*random.choices(self.anyOf, k=len(self.anyOf)))) + for k, v in additional_validations.items(): + setattr(self, k, v) + if self.is_nullable and ( random.uniform(0, 1) < self.allow_none_optionals or context["state"]["__depth__"] > self.max_recursive_depth diff --git a/jsf/schema_types/enum.py b/jsf/schema_types/enum.py index f93b4c3..95d1660 100644 --- a/jsf/schema_types/enum.py +++ b/jsf/schema_types/enum.py @@ -12,7 +12,7 @@ class JSFEnum(BaseSchema): - enum: Optional[List[Union[str, int, float, dict, None]]] = [] + enum: Optional[List[Union[bool, str, int, float, dict, None]]] = [] model_config = ConfigDict() def generate(self, context: Dict[str, Any]) -> Optional[Union[str, int, float]]: diff --git a/jsf/tests/data/bool-enum.json b/jsf/tests/data/bool-enum.json new file mode 100644 index 0000000..f33930e --- /dev/null +++ b/jsf/tests/data/bool-enum.json @@ -0,0 +1,3 @@ +{ + "enum": [true] +} diff --git a/jsf/tests/data/min-required-props.json b/jsf/tests/data/min-required-props.json new file mode 100644 index 0000000..5b4b4f4 --- /dev/null +++ b/jsf/tests/data/min-required-props.json @@ -0,0 +1,15 @@ +{ + "type": "object", + "friendly_name": "Corporate Structure Changes", + "description": "Parameters for corporate structure changes", + "properties": { + "new_parent": { "const": true }, + "new_subsidiary": { "const": true }, + "new_sibling": { "const": true } + }, + "oneOf": [ + { "required": ["new_parent"] }, + { "required": ["new_subsidiary"] }, + { "required": ["new_sibling"] } + ] +} diff --git a/jsf/tests/test_default_fake.py b/jsf/tests/test_default_fake.py index 56b42e6..cafa496 100644 --- a/jsf/tests/test_default_fake.py +++ b/jsf/tests/test_default_fake.py @@ -544,3 +544,26 @@ def test_use_defaults_and_examples(TestData): assert d["name"] in ["Chop", "Luna", "Thanos"] breed = d.get("breed") assert breed is None or breed == "Mixed Breed" + + +def test_min_required_props(TestData): + with open(TestData / "min-required-props.json") as file: + schema = json.load(file) + p = JSF(schema) + fake_data = [p.generate(use_examples=True) for _ in range(100)] + + for d in fake_data: + assert isinstance(d, dict) + assert len(d.keys()) >= 1 + assert all(isinstance(v, bool) for v in d.values()) + assert all(d.values()) + +def test_bool_enum(TestData): + with open(TestData / "bool-enum.json") as file: + schema = json.load(file) + p = JSF(schema) + fake_data = [p.generate() for _ in range(100)] + + for d in fake_data: + assert isinstance(d, bool) + assert d From 528b3051b0f9fac26f7da8c86d06f76033b5e4da Mon Sep 17 00:00:00 2001 From: ghandic Date: Mon, 13 May 2024 22:45:13 +1000 Subject: [PATCH 2/3] Fix formatting --- jsf/schema_types/base.py | 4 ++-- ...red-props.json => min-required-props-oneof.json} | 0 jsf/tests/test_default_fake.py | 13 +++++++------ 3 files changed, 9 insertions(+), 8 deletions(-) rename jsf/tests/data/{min-required-props.json => min-required-props-oneof.json} (100%) diff --git a/jsf/schema_types/base.py b/jsf/schema_types/base.py index 50d8a92..1dea205 100644 --- a/jsf/schema_types/base.py +++ b/jsf/schema_types/base.py @@ -1,7 +1,7 @@ -from collections import ChainMap import logging import random import uuid +from collections import ChainMap from typing import Any, Dict, List, Optional, Tuple, Type, Union from pydantic import BaseModel, Field @@ -28,7 +28,7 @@ class BaseSchema(BaseModel): schema_: Optional[str] = Field(None, alias="$schema") # The $comment keyword is strictly intended for adding comments to the JSON schema source. Its value must always be a string. Unlike the annotations title, description and examples, JSON schema implementations aren't allowed to attach any meaning or behavior to it whatsoever, and may even strip them at any time. Therefore, they are useful for leaving notes to future editors of a JSON schema, (which is quite likely your future self), but should not be used to communicate to users of the schema. comments: Optional[str] = Field(None, alias="$comments") - + oneOf: Optional[List[Dict[str, Any]]] = None anyOf: Optional[List[Dict[str, Any]]] = None allOf: Optional[List[Dict[str, Any]]] = None diff --git a/jsf/tests/data/min-required-props.json b/jsf/tests/data/min-required-props-oneof.json similarity index 100% rename from jsf/tests/data/min-required-props.json rename to jsf/tests/data/min-required-props-oneof.json diff --git a/jsf/tests/test_default_fake.py b/jsf/tests/test_default_fake.py index cafa496..8f0cf0c 100644 --- a/jsf/tests/test_default_fake.py +++ b/jsf/tests/test_default_fake.py @@ -546,24 +546,25 @@ def test_use_defaults_and_examples(TestData): assert breed is None or breed == "Mixed Breed" -def test_min_required_props(TestData): - with open(TestData / "min-required-props.json") as file: +def test_min_required_props_oneof(TestData): + with open(TestData / "min-required-props-oneof.json") as file: schema = json.load(file) p = JSF(schema) fake_data = [p.generate(use_examples=True) for _ in range(100)] - + for d in fake_data: assert isinstance(d, dict) - assert len(d.keys()) >= 1 - assert all(isinstance(v, bool) for v in d.values()) + assert len(d.keys()) == 1 + assert all(isinstance(v, bool) for v in d.values()) assert all(d.values()) + def test_bool_enum(TestData): with open(TestData / "bool-enum.json") as file: schema = json.load(file) p = JSF(schema) fake_data = [p.generate() for _ in range(100)] - + for d in fake_data: assert isinstance(d, bool) assert d From 5caf67e4614fdd0bf6782c0edb2f13fefb7bcd8a Mon Sep 17 00:00:00 2001 From: ghandic Date: Mon, 13 May 2024 22:47:20 +1000 Subject: [PATCH 3/3] Fix test --- jsf/tests/test_default_fake.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsf/tests/test_default_fake.py b/jsf/tests/test_default_fake.py index 8f0cf0c..2a341f5 100644 --- a/jsf/tests/test_default_fake.py +++ b/jsf/tests/test_default_fake.py @@ -554,7 +554,7 @@ def test_min_required_props_oneof(TestData): for d in fake_data: assert isinstance(d, dict) - assert len(d.keys()) == 1 + assert len(d.keys()) >= 1 assert all(isinstance(v, bool) for v in d.values()) assert all(d.values())