Skip to content

Commit

Permalink
Merge pull request #126 from visier/bugfix/VAN-126702-openapi-sqlike-…
Browse files Browse the repository at this point in the history
…oneof

[VAN-126702] SqlLike request fix to retrieve two types of the DTOs
  • Loading branch information
Shmalikov authored Oct 9, 2024
2 parents 57772ef + 6744f7a commit d0d1704
Show file tree
Hide file tree
Showing 2 changed files with 238 additions and 0 deletions.
212 changes: 212 additions & 0 deletions sdk-blueprints/python/api/templates/model_oneof.mustache
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
from __future__ import annotations
import json
import pprint
{{#vendorExtensions.x-py-other-imports}}
{{{.}}}
{{/vendorExtensions.x-py-other-imports}}
{{#vendorExtensions.x-py-model-imports}}
{{{.}}}
{{/vendorExtensions.x-py-model-imports}}
from pydantic import StrictStr, Field
from typing import Union, List, Set, Optional, Dict, ClassVar
from typing_extensions import Literal, Self

{{#lambda.uppercase}}{{{classname}}}{{/lambda.uppercase}}_ONE_OF_SCHEMAS = [{{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}}]

class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}):
"""
{{{description}}}{{^description}}{{{classname}}}{{/description}}
"""
{{#composedSchemas.oneOf}}
# data type: {{{dataType}}}
{{vendorExtensions.x-py-name}}: {{{vendorExtensions.x-py-typing}}}
{{/composedSchemas.oneOf}}
actual_instance: Optional[Union[{{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}]] = None
one_of_schemas: Set[str] = { {{#oneOf}}"{{.}}"{{^-last}}, {{/-last}}{{/oneOf}} }
_default_values: ClassVar[Dict[str, Any]] = { {{#oneOf}}"{{.}}": {{.}}(){{^-last}}, {{/-last}}{{/oneOf}} }

model_config = ConfigDict(
validate_assignment=True,
protected_namespaces=(),
)

{{#discriminator}}

discriminator_value_class_map: Dict[str, str] = {
{{#children}}
'{{^vendorExtensions.x-discriminator-value}}{{name}}{{/vendorExtensions.x-discriminator-value}}{{#vendorExtensions.x-discriminator-value}}{{{vendorExtensions.x-discriminator-value}}}{{/vendorExtensions.x-discriminator-value}}': '{{{classname}}}'{{^-last}},{{/-last}}
{{/children}}
}
{{/discriminator}}

def __init__(self, *args, **kwargs) -> None:
if args:
if len(args) > 1:
raise ValueError("If a position argument is used, only 1 is allowed to set `actual_instance`")
if kwargs:
raise ValueError("If a position argument is used, keyword arguments cannot be used.")
super().__init__(actual_instance=args[0])
else:
super().__init__(**kwargs)

@field_validator('actual_instance')
def actual_instance_must_validate_oneof(cls, v):
{{#isNullable}}
if v is None:
return v

{{/isNullable}}
instance = {{{classname}}}.model_construct()
error_messages = []
match = 0
{{#composedSchemas.oneOf}}
# validate data type: {{{dataType}}}
{{#isContainer}}
try:
instance.{{vendorExtensions.x-py-name}} = v
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isContainer}}
{{^isContainer}}
{{#isPrimitiveType}}
try:
instance.{{vendorExtensions.x-py-name}} = v
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{^isPrimitiveType}}
if not isinstance(v, {{{dataType}}}):
error_messages.append(f"Error! Input type `{type(v)}` is not `{{{dataType}}}`")
else:
match += 1
{{/isPrimitiveType}}
{{/isContainer}}
{{/composedSchemas.oneOf}}
if match > 1:
# more than 1 match
raise ValueError("Multiple matches found when setting `actual_instance` in {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages))
elif match == 0:
# no match
raise ValueError("No match found when setting `actual_instance` in {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages))
else:
return v

@classmethod
def from_dict(cls, obj: Union[str, Dict[str, Any]]) -> Self:
return cls.from_json(json.dumps(obj))

@classmethod
{{#isNullable}}
def from_json(cls, json_str: Optional[str]) -> Self:
{{/isNullable}}
{{^isNullable}}
def from_json(cls, json_str: str) -> Self:
{{/isNullable}}
"""Returns the object represented by the json string"""
instance = cls.model_construct()
{{#isNullable}}
if json_str is None:
return instance

{{/isNullable}}
error_messages = []
match = 0

{{#useOneOfDiscriminatorLookup}}
{{#discriminator}}
{{#mappedModels}}
{{#-first}}
# use oneOf discriminator to lookup the data type
_data_type = json.loads(json_str).get("{{{propertyBaseName}}}")
if not _data_type:
raise ValueError("Failed to lookup data type from the field `{{{propertyBaseName}}}` in the input.")

{{/-first}}
# check if data type is `{{{modelName}}}`
if _data_type == "{{{mappingName}}}":
instance.actual_instance = {{{modelName}}}.from_json(json_str)
return instance

{{/mappedModels}}
{{/discriminator}}
{{/useOneOfDiscriminatorLookup}}
{{#composedSchemas.oneOf}}
{{#isContainer}}
# deserialize data into {{{dataType}}}
try:
# validation
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
# assign value to actual_instance
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isContainer}}
{{^isContainer}}
{{#isPrimitiveType}}
# deserialize data into {{{dataType}}}
try:
# validation
instance.{{vendorExtensions.x-py-name}} = json.loads(json_str)
# assign value to actual_instance
instance.actual_instance = instance.{{vendorExtensions.x-py-name}}
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{^isPrimitiveType}}
# deserialize data into {{{dataType}}}
try:
actual_instance = {{{dataType}}}.from_json(json_str)
if actual_instance != cls._default_values[{{{dataType}}}.__name__]:
instance.actual_instance = actual_instance
match += 1
except (ValidationError, ValueError) as e:
error_messages.append(str(e))
{{/isPrimitiveType}}
{{/isContainer}}
{{/composedSchemas.oneOf}}

if match > 1:
# more than 1 match
raise ValueError("Multiple matches found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages))
elif match == 0:
# no match
raise ValueError("No match found when deserializing the JSON string into {{{classname}}} with oneOf schemas: {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}. Details: " + ", ".join(error_messages))
else:
return instance

def to_json(self) -> str:
"""Returns the JSON representation of the actual instance"""
if self.actual_instance is None:
return "null"

if hasattr(self.actual_instance, "to_json") and callable(self.actual_instance.to_json):
return self.actual_instance.to_json()
else:
return json.dumps(self.actual_instance)

def to_dict(self) -> Optional[Union[Dict[str, Any], {{#oneOf}}{{{.}}}{{^-last}}, {{/-last}}{{/oneOf}}]]:
"""Returns the dict representation of the actual instance"""
if self.actual_instance is None:
return None

if hasattr(self.actual_instance, "to_dict") and callable(self.actual_instance.to_dict):
return self.actual_instance.to_dict()
else:
# primitive type
return self.actual_instance

def to_str(self) -> str:
"""Returns the string representation of the actual instance"""
return pprint.pformat(self.model_dump())

{{#vendorExtensions.x-py-postponed-model-imports.size}}
{{#vendorExtensions.x-py-postponed-model-imports}}
{{{.}}}
{{/vendorExtensions.x-py-postponed-model-imports}}
# TODO: Rewrite to not use raise_errors
{{classname}}.model_rebuild(raise_errors=False)
{{/vendorExtensions.x-py-postponed-model-imports.size}}
26 changes: 26 additions & 0 deletions sdk-blueprints/update-schema.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Internal script for debugging SDK generation locally.
# Run from the root of the openapi-clients repository.

# Exit immediately if any command exits with a non-zero status
set -e

# Define the version of the openapi specification
version="${1:-0.0.001}"

# Define directory where projects are stored
projects_dir="${2:-$HOME/projects}"

vserver_dir="$projects_dir/com.visier.vserver"
python_sdk_repository_dir="$projects_dir/python-sdk/src"

# Before running this script docker image should be build from repository com.visier.containers.openapi
docker container run -u $UID --rm -v "$vserver_dir":/vserver -e VERSION=$version -e OPENAPI_OUTPUT_ROOT="/vserver/target/openapi" -e GENERATE_API_DESCRIPTIONS=false visier-openapi:latest

# Copy openapi specifications to res directory
cp "$vserver_dir/target/openapi"/*.yaml res/

# Run preprocessing openapi specs script
python3 "sdk-blueprints/preprocessing/preprocessing.py" res "sdk-generation-test/specs"

# Run python SDK generation script
./sdk-blueprints/python/generate-all.sh "$python_sdk_repository_dir" "sdk-generation-test/specs"

0 comments on commit d0d1704

Please sign in to comment.