diff --git a/api_generator/api_generator/config.py b/api_generator/api_generator/config.py
index 20080b56b..f40e4a49b 100644
--- a/api_generator/api_generator/config.py
+++ b/api_generator/api_generator/config.py
@@ -10,11 +10,6 @@ class Platform(str, Enum):
WEB = 'web'
-class DescriptionLanguage(str, Enum):
- EN = 'en'
- RU = 'ru'
-
-
class GeneratedLanguage(str, Enum):
SWIFT = 'swift'
KOTLIN = 'kotlin'
diff --git a/api_generator/api_generator/generator.py b/api_generator/api_generator/generator.py
index 830d71ff9..151aeeac4 100644
--- a/api_generator/api_generator/generator.py
+++ b/api_generator/api_generator/generator.py
@@ -1,7 +1,14 @@
from .config import Config, GeneratedLanguage
from .schema.preprocessing import schema_preprocessing
from .schema.modeling import build_objects
-from .generators import Generator, SwiftGenerator, KotlinGenerator, DocumentationGenerator, TypeScriptGenerator
+from .generators import (
+ Generator,
+ SwiftGenerator,
+ KotlinGenerator,
+ DocumentationGenerator,
+ TypeScriptGenerator,
+ PythonGenerator,
+)
def __build_generator(config: Config) -> Generator:
@@ -11,6 +18,7 @@ def __build_generator(config: Config) -> Generator:
GeneratedLanguage.KOTLIN: KotlinGenerator,
GeneratedLanguage.DOCUMENTATION: DocumentationGenerator,
GeneratedLanguage.TYPE_SCRIPT: TypeScriptGenerator,
+ GeneratedLanguage.PYTHON: PythonGenerator,
}
generator = generator_dict.get(lang, None)
if generator is None:
diff --git a/api_generator/api_generator/generators/__init__.py b/api_generator/api_generator/generators/__init__.py
index 091921fc4..06fb1baf5 100644
--- a/api_generator/api_generator/generators/__init__.py
+++ b/api_generator/api_generator/generators/__init__.py
@@ -3,5 +3,6 @@ from .swift import SwiftGenerator
from .kotlin import KotlinGenerator
from .documentation import DocumentationGenerator
from .type_script import TypeScriptGenerator
+from .python import PythonGenerator
-__all__ = [Generator, SwiftGenerator, KotlinGenerator, DocumentationGenerator, TypeScriptGenerator]
+__all__ = [Generator, SwiftGenerator, KotlinGenerator, DocumentationGenerator, TypeScriptGenerator, PythonGenerator]
diff --git a/api_generator/api_generator/generators/base.py b/api_generator/api_generator/generators/base.py
index efc1e799a..e30adfa67 100644
--- a/api_generator/api_generator/generators/base.py
+++ b/api_generator/api_generator/generators/base.py
@@ -65,12 +65,12 @@ class Generator(ABC):
def _generate_files(self, objects: List[Declarable]):
for obj in objects:
declaration = []
- for line in str(self._main_declaration(obj)).strip().split('\n'):
+ for line in str(self._main_declaration(obj)).rstrip().split('\n'):
if line.isspace():
declaration.append('')
else:
declaration.append(line)
- declaration = '\n'.join(declaration).strip()
+ declaration = '\n'.join(declaration)
if not declaration:
continue
head_for_file = self._head_for_file + '\n' if self._head_for_file.strip() else ''
diff --git a/api_generator/api_generator/generators/documentation/generator.py b/api_generator/api_generator/generators/documentation/generator.py
index 1241358b9..8601aab86 100644
--- a/api_generator/api_generator/generators/documentation/generator.py
+++ b/api_generator/api_generator/generators/documentation/generator.py
@@ -11,13 +11,14 @@ from .documentation_entities import (
from .translations import translations
from .utils import bold, code, paragraph
from ..base import Generator
-from ...config import Config, Platform, DescriptionLanguage
+from ...config import Config, Platform
from ...schema.modeling.entities import (
Declarable,
Entity,
EntityEnumeration,
StringEnumeration,
- Property
+ Property,
+ DescriptionLanguage
)
from ...schema.modeling.text import Text
diff --git a/api_generator/api_generator/generators/documentation/translations.py b/api_generator/api_generator/generators/documentation/translations.py
index 03493fb45..01ee82e2e 100644
--- a/api_generator/api_generator/generators/documentation/translations.py
+++ b/api_generator/api_generator/generators/documentation/translations.py
@@ -1,5 +1,5 @@
from typing import Dict
-from ...config import DescriptionLanguage
+from ...schema.modeling.entities import DescriptionLanguage
__full_translations: Dict[str, Dict[str, str]] = {
"div_generator_android": {
diff --git a/api_generator/api_generator/generators/python/__init__.py b/api_generator/api_generator/generators/python/__init__.py
new file mode 100644
index 000000000..65530a5ef
--- /dev/null
+++ b/api_generator/api_generator/generators/python/__init__.py
@@ -0,0 +1,3 @@
+from .generator import PythonGenerator
+
+__all__ = [PythonGenerator]
diff --git a/api_generator/api_generator/generators/python/generator.py b/api_generator/api_generator/generators/python/generator.py
new file mode 100644
index 000000000..39403f930
--- /dev/null
+++ b/api_generator/api_generator/generators/python/generator.py
@@ -0,0 +1,229 @@
+from typing import cast, List
+
+from .python_entities import (
+ update_base,
+ as_python_commentary,
+ _make_python_imports,
+ _python_full_name,
+ PythonEntity,
+ PythonProperty
+)
+from ..base import Generator
+from ...schema.modeling.text import Text, EMPTY
+from ...schema.modeling.entities import (
+ StringEnumeration,
+ EntityEnumeration,
+ Entity,
+ Declarable,
+)
+from ... import utils
+
+
+class PythonGenerator(Generator):
+ def _filename(self, name: str) -> str:
+ return f'{utils.snake_case(name)}.py'
+
+ def generate(self, objects: List[Declarable]):
+ objects_py: List[Declarable] = list(map(lambda obj: update_base(obj), objects))
+ super(PythonGenerator, self).generate(objects_py)
+ self.__generate_package_file(objects_py)
+
+ def __generate_package_file(self, objects: List[Declarable]):
+ file_content = Text('# Generated code. Do not modify.')
+ file_content += EMPTY
+
+ def sort_key(d: Declarable):
+ return d.name
+ uniqs = []
+ for obj in sorted(objects, key=sort_key):
+ uniqs.append(utils.capitalize_camel_case(obj.name))
+ if isinstance(obj, PythonEntity):
+ uniqs.extend(self.__inner_types_classes(obj))
+
+ if len(uniqs) > len(list(set(uniqs))):
+ raise ValueError('Inner type classes not uniqs')
+
+ for obj in sorted(objects, key=sort_key):
+ imports = [utils.capitalize_camel_case(obj.name)]
+ if isinstance(obj, PythonEntity):
+ imports += self.__inner_types_classes(obj)
+ imports = ', '.join(imports)
+ file_content += f'from .{utils.snake_case(obj.name)} import {imports}'
+
+ file_content += EMPTY
+ with_inners = []
+ for obj in sorted(objects, key=sort_key):
+ if isinstance(obj, PythonEntity):
+ with_inners.append(utils.capitalize_camel_case(obj.name))
+ with_inners += self.__inner_entity_classes(obj)
+
+ for e in with_inners:
+ file_content += f'{e}.update_forward_refs()'
+
+ file_content += EMPTY
+
+ with open(f'{self._config.output_path}/__init__.py', 'w') as file:
+ file.write(str(file_content))
+
+ def __inner_types_classes(self, entity: PythonEntity) -> List[str]:
+ def declaration_of(decl_type, method) -> List[str]:
+ def sort_predicate(d: Declarable):
+ return d.name
+ classes = []
+ inner_types = sorted(filter(lambda t: isinstance(t, decl_type), entity.inner_types), key=sort_predicate)
+ for inner_type in inner_types:
+ if isinstance(inner_type, decl_type):
+ classes += method(inner_type)
+ return classes
+
+ result = []
+
+ result.extend(declaration_of(StringEnumeration, lambda str_enum: [_python_full_name(str_enum)]))
+ result.extend(declaration_of(EntityEnumeration, lambda ent_enum: [_python_full_name(ent_enum)]))
+ result.extend(declaration_of(Entity, lambda ent: [_python_full_name(ent)] + self.__inner_types_classes(ent)))
+
+ return result
+
+ def __inner_entity_classes(self, entity: PythonEntity) -> List[str]:
+ result = []
+ def sort_predicate(d: Declarable):
+ return d.name
+
+ for inner_type in sorted(entity.inner_types, key=sort_predicate):
+ if isinstance(inner_type, PythonEntity):
+ result.append(_python_full_name(inner_type))
+ result += self.__inner_entity_classes(inner_type)
+ return result
+
+ def _entity_declaration(self, entity: PythonEntity) -> Text:
+ result = Text()
+ imports = entity.imports
+ if imports is not None:
+ result += imports
+ result += EMPTY
+ result += EMPTY
+ result += self.__entity_declaration_without_imports(entity)
+ return result
+
+ def __entity_declaration_without_imports(self, entity: PythonEntity) -> Text:
+ result = Text()
+ description = entity.description_doc()
+ if description.strip():
+ result += as_python_commentary(description)
+ py_full_name = _python_full_name(entity)
+ result += f'class {py_full_name}(BaseDiv):'
+ result += EMPTY
+ result += entity.generate_init(filename=utils.snake_case(py_full_name)).indented(indent_width=4)
+ if entity.should_generate_as_python_class:
+ declaration = entity.class_properties_declaration
+ else:
+ declaration = entity.dynamic_properties_declaration
+ if declaration.lines:
+ result += EMPTY
+ result += declaration
+
+ inner_types_declaration = self.__inner_types_declaration(entity)
+ if inner_types_declaration.lines:
+ result += EMPTY
+ result += EMPTY
+ result += inner_types_declaration
+
+ result += EMPTY
+ result += EMPTY
+ result += f'{py_full_name}.update_forward_refs()'
+ return result
+
+ def __inner_entity_declaration_without_imports(self, entity: PythonEntity, filename: str) -> Text:
+ result = Text()
+ description = entity.description_doc()
+ if description:
+ result += as_python_commentary(description)
+ py_full_name = _python_full_name(entity)
+ result += f'class {py_full_name}(BaseDiv):'
+ result += EMPTY
+ result += entity.generate_init(filename).indented(indent_width=4)
+
+ if entity.should_generate_as_python_class:
+ declaration = entity.class_properties_declaration
+ else:
+ declaration = Text()
+ for p in cast(List[PythonProperty], entity.instance_properties):
+ declaration += p.make_declaration(filename).indented(indent_width=4)
+ if declaration.lines:
+ result += EMPTY
+ result += declaration
+
+ inner_types_declaration = self.__inner_types_declaration(entity)
+ if inner_types_declaration.lines:
+ result += EMPTY
+ result += EMPTY
+ result += inner_types_declaration
+
+ result += EMPTY
+ result += EMPTY
+ result += f'{py_full_name}.update_forward_refs()'
+ return result
+
+ def __inner_types_declaration(self, entity: PythonEntity) -> Text:
+ def declaration_of(decl_type, decl_method) -> Text:
+ def sort_predicate(d: Declarable):
+ return d.name
+ inner_types_decl = Text()
+ inner_types = sorted(filter(lambda t: isinstance(t, decl_type), entity.inner_types), key=sort_predicate)
+ for ind, inner_type in enumerate(inner_types):
+ inner_types_decl += decl_method(inner_type)
+ if ind != (len(inner_types) - 1):
+ inner_types_decl += EMPTY
+ inner_types_decl += EMPTY
+ return inner_types_decl
+
+ declarations = [declaration_of(StringEnumeration, self._string_enumeration_declaration),
+ declaration_of(EntityEnumeration, self.__entity_enumeration_declaration_without_imports),
+ declaration_of(Entity,
+ lambda prop: self.__inner_entity_declaration_without_imports(
+ prop,
+ utils.snake_case(_python_full_name(entity))
+ ))
+ ]
+ result = Text()
+ declarations = list(filter(lambda d: d.lines, declarations))
+ for ind, decl in enumerate(declarations):
+ result += decl
+ if ind != (len(declarations) - 1):
+ result += EMPTY
+ result += EMPTY
+ return result
+
+ def _entity_enumeration_declaration(self, entity_enumeration: EntityEnumeration) -> Text:
+ result = Text()
+ imports = sorted(list(filter(None,
+ map(
+ lambda e: _python_full_name(e[1]) if e[1] is not None else None,
+ entity_enumeration.entities
+ ))))
+ imports = _make_python_imports(items=imports)
+ if imports != EMPTY:
+ result += imports
+ result += EMPTY
+ result += self.__entity_enumeration_declaration_without_imports(entity_enumeration)
+ return result
+
+ @staticmethod
+ def __entity_enumeration_declaration_without_imports(entity_enumeration: EntityEnumeration) -> Text:
+ result = Text()
+ result += 'from typing import Union'
+ result += EMPTY
+ result += f'{_python_full_name(entity_enumeration)} = Union['
+ entities = list(filter(None, map(lambda e: e[1], entity_enumeration.entities)))
+ for e in entities:
+ full_name = _python_full_name(e)
+ result += Text(f'{utils.snake_case(full_name)}.{full_name},').indented(indent_width=4)
+ result += ']'
+ return result
+
+ def _string_enumeration_declaration(self, string_enumeration: StringEnumeration) -> Text:
+ result = Text()
+ result += f'class {_python_full_name(string_enumeration)}(str, enum.Enum):'
+ for _, value in string_enumeration.cases:
+ result += Text(f"{utils.upper_snake_case(value)} = '{value}'").indented(indent_width=4)
+ return result
diff --git a/api_generator/api_generator/generators/python/python_entities.py b/api_generator/api_generator/generators/python/python_entities.py
new file mode 100644
index 000000000..fc0937e73
--- /dev/null
+++ b/api_generator/api_generator/generators/python/python_entities.py
@@ -0,0 +1,355 @@
+from __future__ import annotations
+from typing import List, Optional, cast
+
+from ...schema.modeling.entities import (
+ Declarable,
+ PropertyType,
+ Property,
+ Entity,
+ Array,
+ Bool,
+ BoolInt,
+ Color,
+ Dictionary,
+ Double,
+ Int,
+ Object,
+ StaticString,
+ String,
+ Url
+)
+from ... import utils
+from ...schema.modeling.text import Text, EMPTY
+
+
+def update_base(obj: Declarable) -> Declarable:
+ if isinstance(obj, Entity):
+ obj.__class__ = PythonEntity
+ cast(PythonEntity, obj).update_base()
+ return obj
+
+
+def update_property_type_base(property_type: PropertyType):
+ if isinstance(property_type, Array):
+ property_type.__class__ = PythonArray
+ cast(PythonArray, property_type).update_base()
+ elif isinstance(property_type, Bool):
+ property_type.__class__ = PythonBool
+ elif isinstance(property_type, BoolInt):
+ property_type.__class__ = PythonBoolInt
+ elif isinstance(property_type, Color):
+ property_type.__class__ = PythonColor
+ elif isinstance(property_type, Dictionary):
+ property_type.__class__ = PythonDictionary
+ elif isinstance(property_type, Double):
+ property_type.__class__ = PythonDouble
+ elif isinstance(property_type, Int):
+ property_type.__class__ = PythonInt
+ elif isinstance(property_type, Object):
+ property_type.__class__ = PythonObject
+ cast(PythonObject, property_type).update_base()
+ elif isinstance(property_type, StaticString):
+ property_type.__class__ = PythonStaticString
+ elif isinstance(property_type, String):
+ property_type.__class__ = PythonString
+ elif isinstance(property_type, Url):
+ property_type.__class__ = PythonUrl
+ else:
+ raise NotImplementedError
+
+
+def _python_full_name(obj: Declarable) -> str:
+ full_name = utils.capitalize_camel_case(obj.name)
+ if obj.parent is not None:
+ full_name = utils.capitalize_camel_case(obj.parent.name) + full_name
+ return full_name
+
+
+def _make_python_imports(items: List[str]) -> Text:
+ if not items:
+ return EMPTY
+ result = Text()
+ for item in items:
+ result += f'from . import {utils.snake_case(item)}'
+ return result
+
+
+def as_python_commentary(s: str) -> Optional[Text]:
+ result = Text()
+ words = s.replace('
', '').replace('', '').split(' ')
+ line = ''
+ for word in words:
+ if not line:
+ line = str(word)
+ elif (len(line) + len(word)) > 80:
+ result += f'# {line}'
+ line = str(word)
+ else:
+ line += f' {word}'
+ result += f'# {line}'
+ return result
+
+
+class PythonEntity(Entity):
+ def update_base(self):
+ for prop in self.properties:
+ prop.__class__ = PythonProperty
+ cast(PythonProperty, prop).update_base()
+ for inner_type in self.inner_types:
+ update_base(inner_type)
+
+ @property
+ def static_properties(self) -> List[Property]:
+ return list(filter(lambda p: isinstance(p.property_type, StaticString), self.properties))
+
+ @property
+ def referenced_top_level_types(self) -> List[str]:
+ return list(filter(None, map(lambda p: p.property_type_py.referenced_top_level_type_name, self.properties)))
+
+ @property
+ def imports(self) -> Optional[Text]:
+ types = self.referenced_top_level_types
+ for inner_type in self.inner_types:
+ if isinstance(inner_type, PythonEntity):
+ types += inner_type.referenced_top_level_types
+ python_full_name = _python_full_name(self)
+ unique_types = list(filter(lambda t: t != python_full_name, set(types)))
+ return None if not unique_types else _make_python_imports(sorted(unique_types))
+
+ def generate_init(self, filename: str) -> Text:
+ main_required = ['self', '*']
+ required = []
+ static_properties = self.static_properties
+ instance_properties = self.instance_properties
+ optional = list(map(lambda p: f'{p.escaped_name}: str = {p.static_value}', static_properties))
+ for p in cast(List[PythonProperty], instance_properties):
+ if p.optional:
+ optional.append(f'{p.escaped_name}: typing.Optional[{p.property_type_py.type_name(filename)}] = None')
+ else:
+ required.append(f'{p.escaped_name}: {p.property_type_py.type_name(filename)}')
+ result = Text()
+ result += 'def __init__('
+ result += Text(', '.join(main_required) + ',').indented(indent_width=4)
+ for p in required + optional:
+ result += Text(p + ',').indented(indent_width=4)
+ result += '):'
+ super_init = Text('super().__init__(')
+ for p in cast(List[PythonProperty], static_properties + instance_properties):
+ escaped_name = p.escaped_name
+ super_init += Text(f'{escaped_name}={escaped_name},').indented(indent_width=4)
+ super_init += ')'
+ result += super_init.indented(indent_width=4)
+ return result
+
+ @property
+ def should_generate_as_python_class(self) -> bool:
+ return len(self.static_properties) != 0
+
+ @property
+ def class_properties_declaration(self) -> Text:
+ result = Text()
+ for p in cast(List[PythonProperty], self.static_properties):
+ field = f'default={p.static_value}'
+ commentary = p.commentary
+ if commentary is not None:
+ commentary = ' '.join(commentary.lines).replace('\n', '').replace('# ', '').replace("'", "\\'")
+ field += f", description='{commentary}'"
+ result += Text(f'{p.escaped_name}: str = Field({field})').indented(indent_width=4)
+ dynamic_declaration = self.dynamic_properties_declaration
+ if dynamic_declaration.lines:
+ result += dynamic_declaration
+ return result
+
+ @property
+ def dynamic_properties_declaration(self) -> Text:
+ result = Text()
+ py_filename = _python_full_name(self)
+ props = cast(List[PythonProperty], self.instance_properties)
+ for ind, p in enumerate(props):
+ result += p.make_declaration(filename=py_filename).indented(indent_width=4)
+ return result
+
+
+class PythonProperty(Property):
+ def update_base(self):
+ update_property_type_base(self.property_type)
+
+ @property
+ def escaped_name(self) -> str:
+ return utils.snake_case(self.name)
+
+ @property
+ def property_type_py(self) -> PythonPropertyType:
+ return cast(PythonPropertyType, self.property_type)
+
+ def constructor_required_value(self, filename: str) -> Optional[str]:
+ if self.optional:
+ return None
+ return f'{self.escaped_name}: {self.property_type_py.type_name(filename)}'
+
+ def constructor_optional_value(self, filename: str) -> Optional[str]:
+ if not self.optional:
+ return None
+ return f'{self.escaped_name}: typing.Optional[{self.property_type_py.type_name(filename)}] = None'
+
+ @property
+ def static_value(self) -> str:
+ if isinstance(self.property_type, StaticString):
+ return f"'{self.property_type.value}'"
+ else:
+ raise TypeError(f'{self.property_type} is not a static type')
+
+ @property
+ def commentary(self) -> Optional[Text]:
+ result = Text()
+ description = self.description_doc()
+ if description:
+ result += as_python_commentary(description)
+ if self.is_deprecated:
+ result += '@deprecated'
+ if result.lines:
+ return result
+ return None
+
+ def make_declaration(self, filename: str) -> Text:
+ fields: List[str] = []
+
+ constraints = self.property_type_py.constraints
+ if constraints:
+ fields.append(constraints)
+
+ commentary = self.commentary
+ if commentary is not None:
+ commentary = ' '.join(commentary.lines).replace('\n', '').replace('# ', '').replace("'", "\\'")
+ fields.append(f"description='{commentary}'")
+
+ if utils.snake_case(self.name) != self.name:
+ fields.append(f"name='{self.name}'")
+
+ if self.optional:
+ prefix = 'typing.Optional['
+ suffix = ']'
+ else:
+ prefix = ''
+ suffix = ''
+ type_decl = f'{prefix}{self.property_type_py.type_name(filename)}{suffix}'
+
+ return Text(f'{self.escaped_name}: {type_decl} = Field({", ".join(fields)})')
+
+
+class PythonPropertyType(PropertyType):
+ @property
+ def referenced_top_level_type_name(self) -> Optional[str]:
+ if isinstance(self, Object):
+ obj = self.object
+ if obj is not None and obj.parent is None:
+ return _python_full_name(obj)
+ return None
+ elif isinstance(self, PythonArray):
+ return self.property_type_py.referenced_top_level_type_name
+ return None
+
+ def type_name(self, filename: str) -> str:
+ if isinstance(self, Int):
+ return 'int'
+ elif isinstance(self, Double):
+ return 'float'
+ elif isinstance(self, (Bool, BoolInt)):
+ return 'bool'
+ elif isinstance(self, (Color, String, Url)):
+ return 'str'
+ elif isinstance(self, StaticString):
+ raise TypeError(f'{self} is a static type')
+ elif isinstance(self, Dictionary):
+ return 'typing.Dict[str, typing.Any]'
+ elif isinstance(self, PythonArray):
+ return f'typing.List[{self.property_type_py.type_name(filename)}]'
+ elif isinstance(self, Object):
+ if self.object is None:
+ raise ValueError(f'Invalid {self}')
+ obj = self.object
+ if utils.snake_case(filename) == utils.snake_case(_python_full_name(obj)) or \
+ obj.parent is not None:
+ return _python_full_name(obj)
+ py_full_name = _python_full_name(obj)
+ return f'{utils.snake_case(py_full_name)}.{py_full_name}'
+
+ @property
+ def constraints(self) -> str:
+ if isinstance(self, (Bool, BoolInt, Dictionary)):
+ return ''
+ elif isinstance(self, Array):
+ result = ''
+ if self.min_items > 0:
+ result += f'min_items={self.min_items}'
+ return result
+ elif isinstance(self, Color):
+ return 'format="color"'
+ elif isinstance(self, (Double, Int)):
+ return ''
+ elif isinstance(self, Object):
+ return ''
+ elif isinstance(self, StaticString):
+ return ''
+ elif isinstance(self, Url):
+ return 'format="uri"'
+ elif isinstance(self, String):
+ result = ''
+ if self.min_length > 0:
+ result += f'min_length={self.min_length}'
+ return result
+ else:
+ raise TypeError
+
+
+class PythonArray(PythonPropertyType, Array):
+ def update_base(self):
+ if not isinstance(self.property_type, PythonPropertyType):
+ update_property_type_base(self.property_type)
+
+ @property
+ def property_type_py(self) -> PythonPropertyType:
+ return cast(PythonPropertyType, self.property_type)
+
+
+class PythonBool(PythonPropertyType, Bool):
+ pass
+
+
+class PythonBoolInt(PythonPropertyType, BoolInt):
+ pass
+
+
+class PythonColor(PythonPropertyType, Color):
+ pass
+
+
+class PythonDictionary(PythonPropertyType, Dictionary):
+ pass
+
+
+class PythonDouble(PythonPropertyType, Double):
+ pass
+
+
+class PythonInt(PythonPropertyType, Int):
+ pass
+
+
+class PythonObject(PythonPropertyType, Object):
+ def update_base(self):
+ if isinstance(self.object, Entity):
+ self.object.__class__ = PythonEntity
+ cast(PythonEntity, self.object).update_base()
+
+
+class PythonStaticString(PythonPropertyType, StaticString):
+ pass
+
+
+class PythonString(PythonPropertyType, String):
+ pass
+
+
+class PythonUrl(PythonPropertyType, Url):
+ pass
diff --git a/api_generator/api_generator/generators/type_script/generator.py b/api_generator/api_generator/generators/type_script/generator.py
index 856224336..9516dba0e 100644
--- a/api_generator/api_generator/generators/type_script/generator.py
+++ b/api_generator/api_generator/generators/type_script/generator.py
@@ -59,7 +59,7 @@ class TypeScriptGenerator(Generator):
return d.name
inner_types_decl = Text()
inner_types = sorted(filter(lambda t: isinstance(t, decl_type), entity.inner_types), key=sort_predicate)
- for ind, p in enumerate(inner_types):
+ for p in inner_types:
if isinstance(p, decl_type):
inner_types_decl += decl_method(p)
inner_types_decl += EMPTY
diff --git a/api_generator/api_generator/schema/modeling/entities.py b/api_generator/api_generator/schema/modeling/entities.py
index 25e6e901a..80feb0de9 100644
--- a/api_generator/api_generator/schema/modeling/entities.py
+++ b/api_generator/api_generator/schema/modeling/entities.py
@@ -10,12 +10,11 @@ import re
from ..utils import is_dict_with_keys_of_type, is_list_of_type
from .utils import (
alias,
- fixing_reserved_typename,
- description_doc
+ fixing_reserved_typename
)
from ...utils import capitalize_camel_case
-from ...config import Config, GeneratedLanguage, GenerationMode, Platform, TEMPLATE_SUFFIX, DescriptionLanguage
+from ...config import Config, GeneratedLanguage, GenerationMode, Platform, TEMPLATE_SUFFIX
from ..preprocessing.entities import ElementLocation
from ..preprocessing.errors import UnresolvedReferenceError
from .errors import GenericError, InvalidFieldRepresentationError
@@ -23,6 +22,18 @@ from .errors import GenericError, InvalidFieldRepresentationError
from . import builders
+class DescriptionLanguage(str, Enum):
+ EN = 'en'
+ RU = 'ru'
+
+
+def description_doc(description_translations: Dict[str, str], lang: DescriptionLanguage, description: str) -> str:
+ try:
+ return description_translations[lang.value]
+ except KeyError:
+ return description
+
+
class Declarable(ABC):
def __init__(self) -> None:
self._parent: Optional[Declarable] = None
diff --git a/api_generator/api_generator/schema/modeling/utils.py b/api_generator/api_generator/schema/modeling/utils.py
index 9d46a4136..f5282ba9c 100644
--- a/api_generator/api_generator/schema/modeling/utils.py
+++ b/api_generator/api_generator/schema/modeling/utils.py
@@ -1,6 +1,6 @@
from typing import Dict, Optional, List
-from ...config import GeneratedLanguage, Platform, DescriptionLanguage
+from ...config import GeneratedLanguage, Platform
from ..utils import get_value_with_optional_by_lang
@@ -24,10 +24,3 @@ def platforms(dictionary: Dict[str, any]) -> Optional[List[Platform]]:
if platforms_list is None:
return None
return list(map(lambda raw: Platform[raw.upper()], platforms_list))
-
-
-def description_doc(description_translations: Dict[str, str], lang: DescriptionLanguage, description: str) -> str:
- try:
- return description_translations[lang.value]
- except KeyError:
- return description
diff --git a/api_generator/api_generator/utils.py b/api_generator/api_generator/utils.py
index ca3bf6233..6df876a4e 100644
--- a/api_generator/api_generator/utils.py
+++ b/api_generator/api_generator/utils.py
@@ -28,6 +28,14 @@ def capitalize_camel_case(string: str) -> str:
return ''.join(map(lambda component: component.capitalize(), name_components(string)))
+def snake_case(string: str) -> str:
+ return '_'.join(map(lambda component: component.lower(), filter(lambda s: s, name_components(string))))
+
+
+def upper_snake_case(string: str) -> str:
+ return '_'.join(map(lambda component: component.upper(), filter(lambda s: s, name_components(string))))
+
+
def lower_camel_case(string: str) -> str:
components = name_components(string)
if not components:
diff --git a/api_generator/tests/configs/python_config.json b/api_generator/tests/configs/python_config.json
new file mode 100644
index 000000000..2faf5f91e
--- /dev/null
+++ b/api_generator/tests/configs/python_config.json
@@ -0,0 +1,4 @@
+{
+ "lang": "python",
+ "header": "# Generated code. Do not modify.\n\nfrom __future__ import annotations\nfrom pydivkit.core import BaseDiv, Field\nimport enum\nimport typing\n"
+}
diff --git a/api_generator/tests/references/python/__init__.py b/api_generator/tests/references/python/__init__.py
new file mode 100644
index 000000000..73b237272
--- /dev/null
+++ b/api_generator/tests/references/python/__init__.py
@@ -0,0 +1,43 @@
+# Generated code. Do not modify.
+
+from .entity import Entity
+from .entity_with_array import EntityWithArray
+from .entity_with_array_of_nested_items import EntityWithArrayOfNestedItems, EntityWithArrayOfNestedItemsItem
+from .entity_with_array_with_transform import EntityWithArrayWithTransform
+from .entity_with_complex_property import EntityWithComplexProperty, EntityWithComplexPropertyProperty
+from .entity_with_complex_property_with_default_value import EntityWithComplexPropertyWithDefaultValue, EntityWithComplexPropertyWithDefaultValueProperty
+from .entity_with_entity_property import EntityWithEntityProperty
+from .entity_with_optional_complex_property import EntityWithOptionalComplexProperty, EntityWithOptionalComplexPropertyProperty
+from .entity_with_optional_property import EntityWithOptionalProperty
+from .entity_with_optional_string_enum_property import EntityWithOptionalStringEnumProperty, EntityWithOptionalStringEnumPropertyProperty
+from .entity_with_property_with_default_value import EntityWithPropertyWithDefaultValue, EntityWithPropertyWithDefaultValueNested
+from .entity_with_required_property import EntityWithRequiredProperty
+from .entity_with_simple_properties import EntityWithSimpleProperties
+from .entity_with_strict_array import EntityWithStrictArray
+from .entity_with_string_array_property import EntityWithStringArrayProperty
+from .entity_with_string_enum_property import EntityWithStringEnumProperty, EntityWithStringEnumPropertyProperty
+from .entity_with_string_enum_property_with_default_value import EntityWithStringEnumPropertyWithDefaultValue, EntityWithStringEnumPropertyWithDefaultValueValue
+from .entity_without_properties import EntityWithoutProperties
+
+EntityWithArray.update_forward_refs()
+EntityWithArrayOfNestedItems.update_forward_refs()
+EntityWithArrayOfNestedItemsItem.update_forward_refs()
+EntityWithArrayWithTransform.update_forward_refs()
+EntityWithComplexProperty.update_forward_refs()
+EntityWithComplexPropertyProperty.update_forward_refs()
+EntityWithComplexPropertyWithDefaultValue.update_forward_refs()
+EntityWithComplexPropertyWithDefaultValueProperty.update_forward_refs()
+EntityWithEntityProperty.update_forward_refs()
+EntityWithOptionalComplexProperty.update_forward_refs()
+EntityWithOptionalComplexPropertyProperty.update_forward_refs()
+EntityWithOptionalProperty.update_forward_refs()
+EntityWithOptionalStringEnumProperty.update_forward_refs()
+EntityWithPropertyWithDefaultValue.update_forward_refs()
+EntityWithPropertyWithDefaultValueNested.update_forward_refs()
+EntityWithRequiredProperty.update_forward_refs()
+EntityWithSimpleProperties.update_forward_refs()
+EntityWithStrictArray.update_forward_refs()
+EntityWithStringArrayProperty.update_forward_refs()
+EntityWithStringEnumProperty.update_forward_refs()
+EntityWithStringEnumPropertyWithDefaultValue.update_forward_refs()
+EntityWithoutProperties.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity.py b/api_generator/tests/references/python/entity.py
new file mode 100644
index 000000000..c2789a635
--- /dev/null
+++ b/api_generator/tests/references/python/entity.py
@@ -0,0 +1,46 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+from . import entity_with_array
+from . import entity_with_array_of_nested_items
+from . import entity_with_array_with_transform
+from . import entity_with_complex_property
+from . import entity_with_complex_property_with_default_value
+from . import entity_with_entity_property
+from . import entity_with_optional_complex_property
+from . import entity_with_optional_property
+from . import entity_with_optional_string_enum_property
+from . import entity_with_property_with_default_value
+from . import entity_with_required_property
+from . import entity_with_simple_properties
+from . import entity_with_strict_array
+from . import entity_with_string_array_property
+from . import entity_with_string_enum_property
+from . import entity_with_string_enum_property_with_default_value
+from . import entity_without_properties
+
+from typing import Union
+
+Entity = Union[
+ entity_with_array.EntityWithArray,
+ entity_with_array_of_nested_items.EntityWithArrayOfNestedItems,
+ entity_with_array_with_transform.EntityWithArrayWithTransform,
+ entity_with_complex_property.EntityWithComplexProperty,
+ entity_with_complex_property_with_default_value.EntityWithComplexPropertyWithDefaultValue,
+ entity_with_entity_property.EntityWithEntityProperty,
+ entity_with_optional_complex_property.EntityWithOptionalComplexProperty,
+ entity_with_optional_property.EntityWithOptionalProperty,
+ entity_with_optional_string_enum_property.EntityWithOptionalStringEnumProperty,
+ entity_with_property_with_default_value.EntityWithPropertyWithDefaultValue,
+ entity_with_required_property.EntityWithRequiredProperty,
+ entity_with_simple_properties.EntityWithSimpleProperties,
+ entity_with_strict_array.EntityWithStrictArray,
+ entity_with_string_array_property.EntityWithStringArrayProperty,
+ entity_with_string_enum_property.EntityWithStringEnumProperty,
+ entity_with_string_enum_property_with_default_value.EntityWithStringEnumPropertyWithDefaultValue,
+ entity_without_properties.EntityWithoutProperties,
+]
diff --git a/api_generator/tests/references/python/entity_with_array.py b/api_generator/tests/references/python/entity_with_array.py
new file mode 100644
index 000000000..2456e186f
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_array.py
@@ -0,0 +1,27 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+from . import entity
+
+
+class EntityWithArray(BaseDiv):
+
+ def __init__(
+ self, *,
+ array: typing.List[entity.Entity],
+ type: str = 'entity_with_array',
+ ):
+ super().__init__(
+ type=type,
+ array=array,
+ )
+
+ type: str = Field(default='entity_with_array')
+ array: typing.List[entity.Entity] = Field(min_items=1)
+
+
+EntityWithArray.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_array_of_nested_items.py b/api_generator/tests/references/python/entity_with_array_of_nested_items.py
new file mode 100644
index 000000000..f6f191b88
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_array_of_nested_items.py
@@ -0,0 +1,46 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+from . import entity
+
+
+class EntityWithArrayOfNestedItems(BaseDiv):
+
+ def __init__(
+ self, *,
+ items: typing.List[EntityWithArrayOfNestedItemsItem],
+ type: str = 'entity_with_array_of_nested_items',
+ ):
+ super().__init__(
+ type=type,
+ items=items,
+ )
+
+ type: str = Field(default='entity_with_array_of_nested_items')
+ items: typing.List[EntityWithArrayOfNestedItemsItem] = Field(min_items=1)
+
+
+class EntityWithArrayOfNestedItemsItem(BaseDiv):
+
+ def __init__(
+ self, *,
+ entity: entity.Entity,
+ property: str,
+ ):
+ super().__init__(
+ entity=entity,
+ property=property,
+ )
+
+ entity: entity.Entity = Field()
+ property: str = Field(min_length=1)
+
+
+EntityWithArrayOfNestedItemsItem.update_forward_refs()
+
+
+EntityWithArrayOfNestedItems.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_array_with_transform.py b/api_generator/tests/references/python/entity_with_array_with_transform.py
new file mode 100644
index 000000000..d8f24e110
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_array_with_transform.py
@@ -0,0 +1,25 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithArrayWithTransform(BaseDiv):
+
+ def __init__(
+ self, *,
+ array: typing.List[str],
+ type: str = 'entity_with_array_with_transform',
+ ):
+ super().__init__(
+ type=type,
+ array=array,
+ )
+
+ type: str = Field(default='entity_with_array_with_transform')
+ array: typing.List[str] = Field(min_items=1)
+
+
+EntityWithArrayWithTransform.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_complex_property.py b/api_generator/tests/references/python/entity_with_complex_property.py
new file mode 100644
index 000000000..ae1a2d7b5
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_complex_property.py
@@ -0,0 +1,41 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithComplexProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ property: EntityWithComplexPropertyProperty,
+ type: str = 'entity_with_complex_property',
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_complex_property')
+ property: EntityWithComplexPropertyProperty = Field()
+
+
+class EntityWithComplexPropertyProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ value: str,
+ ):
+ super().__init__(
+ value=value,
+ )
+
+ value: str = Field(format="uri")
+
+
+EntityWithComplexPropertyProperty.update_forward_refs()
+
+
+EntityWithComplexProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_complex_property_with_default_value.py b/api_generator/tests/references/python/entity_with_complex_property_with_default_value.py
new file mode 100644
index 000000000..769a26578
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_complex_property_with_default_value.py
@@ -0,0 +1,41 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithComplexPropertyWithDefaultValue(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_complex_property_with_default_value',
+ property: typing.Optional[EntityWithComplexPropertyWithDefaultValueProperty] = None,
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_complex_property_with_default_value')
+ property: typing.Optional[EntityWithComplexPropertyWithDefaultValueProperty] = Field()
+
+
+class EntityWithComplexPropertyWithDefaultValueProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ value: str,
+ ):
+ super().__init__(
+ value=value,
+ )
+
+ value: str = Field()
+
+
+EntityWithComplexPropertyWithDefaultValueProperty.update_forward_refs()
+
+
+EntityWithComplexPropertyWithDefaultValue.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_entity_property.py b/api_generator/tests/references/python/entity_with_entity_property.py
new file mode 100644
index 000000000..3db16e048
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_entity_property.py
@@ -0,0 +1,27 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+from . import entity
+
+
+class EntityWithEntityProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_entity_property',
+ entity: typing.Optional[entity.Entity] = None,
+ ):
+ super().__init__(
+ type=type,
+ entity=entity,
+ )
+
+ type: str = Field(default='entity_with_entity_property')
+ entity: typing.Optional[entity.Entity] = Field()
+
+
+EntityWithEntityProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_optional_complex_property.py b/api_generator/tests/references/python/entity_with_optional_complex_property.py
new file mode 100644
index 000000000..e8a29f575
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_optional_complex_property.py
@@ -0,0 +1,41 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithOptionalComplexProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_optional_complex_property',
+ property: typing.Optional[EntityWithOptionalComplexPropertyProperty] = None,
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_optional_complex_property')
+ property: typing.Optional[EntityWithOptionalComplexPropertyProperty] = Field()
+
+
+class EntityWithOptionalComplexPropertyProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ value: str,
+ ):
+ super().__init__(
+ value=value,
+ )
+
+ value: str = Field(format="uri")
+
+
+EntityWithOptionalComplexPropertyProperty.update_forward_refs()
+
+
+EntityWithOptionalComplexProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_optional_property.py b/api_generator/tests/references/python/entity_with_optional_property.py
new file mode 100644
index 000000000..f440abadd
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_optional_property.py
@@ -0,0 +1,25 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithOptionalProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_optional_property',
+ property: typing.Optional[str] = None,
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_optional_property')
+ property: typing.Optional[str] = Field(min_length=1)
+
+
+EntityWithOptionalProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_optional_string_enum_property.py b/api_generator/tests/references/python/entity_with_optional_string_enum_property.py
new file mode 100644
index 000000000..fbbf700b3
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_optional_string_enum_property.py
@@ -0,0 +1,30 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithOptionalStringEnumProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_optional_string_enum_property',
+ property: typing.Optional[EntityWithOptionalStringEnumPropertyProperty] = None,
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_optional_string_enum_property')
+ property: typing.Optional[EntityWithOptionalStringEnumPropertyProperty] = Field()
+
+
+class EntityWithOptionalStringEnumPropertyProperty(str, enum.Enum):
+ FIRST = 'first'
+ SECOND = 'second'
+
+
+EntityWithOptionalStringEnumProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_property_with_default_value.py b/api_generator/tests/references/python/entity_with_property_with_default_value.py
new file mode 100644
index 000000000..1f793b59f
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_property_with_default_value.py
@@ -0,0 +1,55 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithPropertyWithDefaultValue(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_property_with_default_value',
+ int: typing.Optional[int] = None,
+ nested: typing.Optional[EntityWithPropertyWithDefaultValueNested] = None,
+ url: typing.Optional[str] = None,
+ ):
+ super().__init__(
+ type=type,
+ int=int,
+ nested=nested,
+ url=url,
+ )
+
+ type: str = Field(default='entity_with_property_with_default_value')
+ int: typing.Optional[int] = Field()
+ nested: typing.Optional[EntityWithPropertyWithDefaultValueNested] = Field(description='non_optional is used to suppress auto-generation of default value for object withall-optional fields.')
+ url: typing.Optional[str] = Field(format="uri")
+
+
+# non_optional is used to suppress auto-generation of default value for object with
+# all-optional fields.
+class EntityWithPropertyWithDefaultValueNested(BaseDiv):
+
+ def __init__(
+ self, *,
+ non_optional: str,
+ int: typing.Optional[int] = None,
+ url: typing.Optional[str] = None,
+ ):
+ super().__init__(
+ int=int,
+ non_optional=non_optional,
+ url=url,
+ )
+
+ int: typing.Optional[int] = Field()
+ non_optional: str = Field()
+ url: typing.Optional[str] = Field(format="uri")
+
+
+EntityWithPropertyWithDefaultValueNested.update_forward_refs()
+
+
+EntityWithPropertyWithDefaultValue.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_required_property.py b/api_generator/tests/references/python/entity_with_required_property.py
new file mode 100644
index 000000000..1b2492b68
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_required_property.py
@@ -0,0 +1,25 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithRequiredProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ property: str,
+ type: str = 'entity_with_required_property',
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_required_property')
+ property: str = Field(min_length=1)
+
+
+EntityWithRequiredProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_simple_properties.py b/api_generator/tests/references/python/entity_with_simple_properties.py
new file mode 100644
index 000000000..22661b369
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_simple_properties.py
@@ -0,0 +1,50 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+# Entity with simple properties.
+class EntityWithSimpleProperties(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_simple_properties',
+ boolean: typing.Optional[bool] = None,
+ boolean_int: typing.Optional[bool] = None,
+ color: typing.Optional[str] = None,
+ double: typing.Optional[float] = None,
+ id: typing.Optional[int] = None,
+ integer: typing.Optional[int] = None,
+ positive_integer: typing.Optional[int] = None,
+ string: typing.Optional[str] = None,
+ url: typing.Optional[str] = None,
+ ):
+ super().__init__(
+ type=type,
+ boolean=boolean,
+ boolean_int=boolean_int,
+ color=color,
+ double=double,
+ id=id,
+ integer=integer,
+ positive_integer=positive_integer,
+ string=string,
+ url=url,
+ )
+
+ type: str = Field(default='entity_with_simple_properties')
+ boolean: typing.Optional[bool] = Field(description='Boolean property.')
+ boolean_int: typing.Optional[bool] = Field(description='Boolean value in numeric format. @deprecated')
+ color: typing.Optional[str] = Field(format="color", description='Color.')
+ double: typing.Optional[float] = Field(description='Floating point number.')
+ id: typing.Optional[int] = Field(description='ID. Can\'t contain expressions.')
+ integer: typing.Optional[int] = Field(description='Integer.')
+ positive_integer: typing.Optional[int] = Field(description='Positive integer.')
+ string: typing.Optional[str] = Field(min_length=1, description='String.')
+ url: typing.Optional[str] = Field(format="uri")
+
+
+EntityWithSimpleProperties.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_strict_array.py b/api_generator/tests/references/python/entity_with_strict_array.py
new file mode 100644
index 000000000..0b7d9f378
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_strict_array.py
@@ -0,0 +1,27 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+from . import entity
+
+
+class EntityWithStrictArray(BaseDiv):
+
+ def __init__(
+ self, *,
+ array: typing.List[entity.Entity],
+ type: str = 'entity_with_strict_array',
+ ):
+ super().__init__(
+ type=type,
+ array=array,
+ )
+
+ type: str = Field(default='entity_with_strict_array')
+ array: typing.List[entity.Entity] = Field(min_items=1)
+
+
+EntityWithStrictArray.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_string_array_property.py b/api_generator/tests/references/python/entity_with_string_array_property.py
new file mode 100644
index 000000000..335f3bbf4
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_string_array_property.py
@@ -0,0 +1,25 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithStringArrayProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ array: typing.List[str],
+ type: str = 'entity_with_string_array_property',
+ ):
+ super().__init__(
+ type=type,
+ array=array,
+ )
+
+ type: str = Field(default='entity_with_string_array_property')
+ array: typing.List[str] = Field(min_items=1)
+
+
+EntityWithStringArrayProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_string_enum_property.py b/api_generator/tests/references/python/entity_with_string_enum_property.py
new file mode 100644
index 000000000..f647599be
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_string_enum_property.py
@@ -0,0 +1,30 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithStringEnumProperty(BaseDiv):
+
+ def __init__(
+ self, *,
+ property: EntityWithStringEnumPropertyProperty,
+ type: str = 'entity_with_string_enum_property',
+ ):
+ super().__init__(
+ type=type,
+ property=property,
+ )
+
+ type: str = Field(default='entity_with_string_enum_property')
+ property: EntityWithStringEnumPropertyProperty = Field()
+
+
+class EntityWithStringEnumPropertyProperty(str, enum.Enum):
+ FIRST = 'first'
+ SECOND = 'second'
+
+
+EntityWithStringEnumProperty.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_with_string_enum_property_with_default_value.py b/api_generator/tests/references/python/entity_with_string_enum_property_with_default_value.py
new file mode 100644
index 000000000..f4c47b66e
--- /dev/null
+++ b/api_generator/tests/references/python/entity_with_string_enum_property_with_default_value.py
@@ -0,0 +1,31 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithStringEnumPropertyWithDefaultValue(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_with_string_enum_property_with_default_value',
+ value: typing.Optional[EntityWithStringEnumPropertyWithDefaultValueValue] = None,
+ ):
+ super().__init__(
+ type=type,
+ value=value,
+ )
+
+ type: str = Field(default='entity_with_string_enum_property_with_default_value')
+ value: typing.Optional[EntityWithStringEnumPropertyWithDefaultValueValue] = Field()
+
+
+class EntityWithStringEnumPropertyWithDefaultValueValue(str, enum.Enum):
+ FIRST = 'first'
+ SECOND = 'second'
+ THIRD = 'third'
+
+
+EntityWithStringEnumPropertyWithDefaultValue.update_forward_refs()
diff --git a/api_generator/tests/references/python/entity_without_properties.py b/api_generator/tests/references/python/entity_without_properties.py
new file mode 100644
index 000000000..640102512
--- /dev/null
+++ b/api_generator/tests/references/python/entity_without_properties.py
@@ -0,0 +1,22 @@
+# Generated code. Do not modify.
+
+from __future__ import annotations
+from pydivkit.core import BaseDiv, Field
+import enum
+import typing
+
+
+class EntityWithoutProperties(BaseDiv):
+
+ def __init__(
+ self, *,
+ type: str = 'entity_without_properties',
+ ):
+ super().__init__(
+ type=type,
+ )
+
+ type: str = Field(default='entity_without_properties')
+
+
+EntityWithoutProperties.update_forward_refs()
diff --git a/api_generator/tests/test_api_generator.py b/api_generator/tests/test_api_generator.py
index 2f83f1004..fb63a5208 100644
--- a/api_generator/tests/test_api_generator.py
+++ b/api_generator/tests/test_api_generator.py
@@ -69,6 +69,12 @@ def test_type_script_generator():
references_folder_name='type_script')
+def test_python_generator():
+ assert_test_generator(config_filename='python_config.json',
+ schema_path=TEST_SCHEMA_PATH,
+ references_folder_name='python')
+
+
def assert_test_generator(config_filename: str, schema_path: str, references_folder_name: str):
config = Config(config_path=utils.path_generator_tests(os.path.join('configs', config_filename)),
schema_path=schema_path,