From e279cc0678946dfb2d9bc15aad15d3a74a892281 Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sat, 20 Feb 2021 02:08:40 -0600 Subject: [PATCH 1/6] Add Enum to Schema generation --- json_schema_to_class.py | 43 +++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/json_schema_to_class.py b/json_schema_to_class.py index b8c2d4c..4c3d0e2 100644 --- a/json_schema_to_class.py +++ b/json_schema_to_class.py @@ -190,6 +190,30 @@ def to_class_code(self, level: int = 0, schema: dict = None) -> str: return indent_class(code=code, level=level) +class Enum(Array): + def __init__(self, name: str, items: type, default: Any = None): + super().__init__(name=name, items=items, default=default) + self.enumlist = items + + def to_init_code(self) -> str: + Array.use_enum = True + + return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", None))'.format( + spaces=spaces(2), + name=self.name, + class_name=self.class_name(), + ) + + def to_class_code(self, level: int = 0, schema: dict = None) -> str: + result = [f'class {self.class_name()}(Enum):'] + self.generate_schema_code(result, schema) + for value in self.enumlist: + result.append(f'{spaces(1)}{value.capitalize()} = "{value}"') + self.generate_validate_code(result, schema) + code = Config.line_break.join(result) + return indent_class(code=code, level=level) + + class Parser: def __init__(self): self.definitions: Dict[str, Item] = {} @@ -211,19 +235,20 @@ def parse_definition(self, name: str, schema: dict) -> Item: if 'type' in schema: item_type = schema['type'] - if item_type == 'object' and 'properties' in schema: + if 'enum' in schema: + Array.use_enum = True + enum_list = schema['enum'] + assert len(enum_list) > 0, "Enum List is Empty" + first = enum_list[0] + assert all(type(first) == type(item) for item in enum_list), "Items in Enum List with Different Types" + assert type(first) in {int, float, str}, "Enum Type is not int, float or string" + return Enum(name=name, items=enum_list, default=default) + elif item_type == 'object' and 'properties' in schema: return self.parse_object(name=name, schema=schema) elif item_type == 'array': return self.parse_array(name=name, schema=schema) else: return Basic(name=name, typename=Basic.TYPE_MAP[item_type], default=default) - elif 'enum' in schema: - enum_list = schema['enum'] - assert len(enum_list) > 0, "Enum List is Empty" - first = enum_list[0] - assert all(type(first) == type(item) for item in enum_list), "Items in Enum List with Different Types" - assert type(first) in {int, float, str}, "Enum Type is not int, float or string" - return Basic(name=name, typename=type(first), default=default) elif '$ref' in schema: path: str = schema['$ref'] class_type = path.split('/')[-1] @@ -251,6 +276,8 @@ def generate(self, schema: dict) -> str: headers = [] if Array.use_list: headers += ['from typing import List'] + if Array.use_enum: + headers += ['from enum import Enum'] if Config.generate_validate_code: headers = ['import json'] + headers + ['', 'import jsonschema'] if Config.generated_warning: From f67012ff144cd1efc8f4b62a348ea1c427cc7423 Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sat, 20 Feb 2021 02:23:09 -0600 Subject: [PATCH 2/6] Fix UT Failures - pass 1 --- json_schema_to_class.py | 1 + tests/test_json_schema_to_class.py | 23 ++++++++++++----------- tests/test_schema.json | 1 + 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/json_schema_to_class.py b/json_schema_to_class.py index 4c3d0e2..bf87299 100644 --- a/json_schema_to_class.py +++ b/json_schema_to_class.py @@ -266,6 +266,7 @@ def parse(self, schema: dict): def generate(self, schema: dict) -> str: Array.use_list = False + Array.use_enum = False result = [] for _, definition in self.definitions.items(): result.append(definition.to_class_code(level=0)) diff --git a/tests/test_json_schema_to_class.py b/tests/test_json_schema_to_class.py index 79997ec..0b15983 100644 --- a/tests/test_json_schema_to_class.py +++ b/tests/test_json_schema_to_class.py @@ -70,19 +70,20 @@ def test_array(self): self.assertTrue(True) def test_enum(self): - def parse_empty_definition(): - parser = json_schema_to_class.Parser() - parser.parse(schema={ - 'title': 'days', - 'type': 'object', - 'properties': { - 'mode': { - 'enum': ['cosine', 'linear', 0] - } + parser = json_schema_to_class.Parser() + parser.parse(schema={ + 'title': 'days', + 'type': 'object', + 'properties': { + 'mode': { + 'enum': ['cosine', 'linear', 0] } - }) + } + }) - self.assertRaises(AssertionError, parse_empty_definition) + code = parser.root.to_class_code() + exec(code) + self.assertTrue(True) def test_parse(self): def parse_empty_definition(): diff --git a/tests/test_schema.json b/tests/test_schema.json index 9598332..13843bf 100644 --- a/tests/test_schema.json +++ b/tests/test_schema.json @@ -24,6 +24,7 @@ "type": "object", "properties": { "lr_mode": { + "type": "string", "enum": [ "step", "cos" From 5d7e40b314506c30477f407907f51a8c9779abc8 Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sat, 20 Feb 2021 02:30:16 -0600 Subject: [PATCH 3/6] Fix UT Failures - pass 2 --- json_schema_to_class.py | 3 ++- tests/test_json_schema_to_class.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/json_schema_to_class.py b/json_schema_to_class.py index bf87299..778c17e 100644 --- a/json_schema_to_class.py +++ b/json_schema_to_class.py @@ -198,10 +198,11 @@ def __init__(self, name: str, items: type, default: Any = None): def to_init_code(self) -> str: Array.use_enum = True - return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", None))'.format( + return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", {default}}))'.format( spaces=spaces(2), name=self.name, class_name=self.class_name(), + default=self.default ) def to_class_code(self, level: int = 0, schema: dict = None) -> str: diff --git a/tests/test_json_schema_to_class.py b/tests/test_json_schema_to_class.py index 0b15983..32164ae 100644 --- a/tests/test_json_schema_to_class.py +++ b/tests/test_json_schema_to_class.py @@ -76,7 +76,8 @@ def test_enum(self): 'type': 'object', 'properties': { 'mode': { - 'enum': ['cosine', 'linear', 0] + 'type': "string", + 'enum': ['cosine', 'linear'] } } }) From 128c2859c4b787cd151e19a9c2e4bf9c9691e95d Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sat, 20 Feb 2021 02:32:27 -0600 Subject: [PATCH 4/6] Fix UT Failures - pass 3 --- json_schema_to_class.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/json_schema_to_class.py b/json_schema_to_class.py index 778c17e..ddb90dc 100644 --- a/json_schema_to_class.py +++ b/json_schema_to_class.py @@ -198,7 +198,7 @@ def __init__(self, name: str, items: type, default: Any = None): def to_init_code(self) -> str: Array.use_enum = True - return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", {default}}))'.format( + return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", "{default}"))'.format( spaces=spaces(2), name=self.name, class_name=self.class_name(), From c384b86983bd1432c4bd8c650a7b19811203aa88 Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Sat, 20 Feb 2021 02:35:22 -0600 Subject: [PATCH 5/6] Fix UT Failures - pass 4 --- tests/test_json_schema_to_class.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_json_schema_to_class.py b/tests/test_json_schema_to_class.py index 32164ae..fee6909 100644 --- a/tests/test_json_schema_to_class.py +++ b/tests/test_json_schema_to_class.py @@ -2,6 +2,7 @@ import json import unittest from pathlib import Path +from enum import Enum import json_schema_to_class From 99754bfde4b18552caab0bde60fb03767f0a137d Mon Sep 17 00:00:00 2001 From: Steven Bell Date: Fri, 26 Feb 2021 17:19:17 -0600 Subject: [PATCH 6/6] 2 fixes 1) If no default is specified, don't have the enum on the object since it'll fail Enum evaluation. Instead, set it to None and override it. 2) If "None" is a value in the provided Enum, lowercase it since None is a reserved word. --- json_schema_to_class.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/json_schema_to_class.py b/json_schema_to_class.py index ddb90dc..7c1c677 100644 --- a/json_schema_to_class.py +++ b/json_schema_to_class.py @@ -198,7 +198,15 @@ def __init__(self, name: str, items: type, default: Any = None): def to_init_code(self) -> str: Array.use_enum = True - return '{spaces}self.{name}: {class_name} = self.{class_name}(values.get("{name}", "{default}"))'.format( + if self.default is None: + return '{if_spaces}self.{name} = None\n{if_spaces}if "{name}" in values:\n{statement_spaces}self.{name} = self.{class_name}(values.get("{name}"))'.format( + if_spaces=spaces(2), + statement_spaces=spaces(3), + name=self.name, + class_name=self.class_name(), + default=self.default + ) + return '{spaces}self.{name} = self.{class_name}(values.get("{name}", "{default}"))'.format( spaces=spaces(2), name=self.name, class_name=self.class_name(), @@ -209,7 +217,11 @@ def to_class_code(self, level: int = 0, schema: dict = None) -> str: result = [f'class {self.class_name()}(Enum):'] self.generate_schema_code(result, schema) for value in self.enumlist: - result.append(f'{spaces(1)}{value.capitalize()} = "{value}"') + if value == "NONE": + result.append(f'{spaces(1)}{value.lower()} = "{value}"') + else: + result.append(f'{spaces(1)}{value.capitalize()} = "{value}"') + result.append(f'\n{spaces(1)}def __str__(self):\n{spaces(2)}return str(self.value)') self.generate_validate_code(result, schema) code = Config.line_break.join(result) return indent_class(code=code, level=level)