Skip to content

Commit

Permalink
Add UUID format support (#1743)
Browse files Browse the repository at this point in the history
* change formatter

* Adds UUID type

* change uuid order

* pre-commit fixes

* Update api template

* Update openapi.yaml remove added format

* pre-commit fixes

* add de/serialization tests

* pre-commit fixes

* test if adding format

* pre-commit fixes

* remove open api change

* Raise the error instead of print

* pre-commit fixes

---------

Co-authored-by: ci.datadog-api-spec <[email protected]>
Co-authored-by: api-clients-generation-pipeline[bot] <54105614+api-clients-generation-pipeline[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Oct 27, 2023
1 parent 45c4c4d commit 5fdcab6
Show file tree
Hide file tree
Showing 43 changed files with 162 additions and 56 deletions.
10 changes: 8 additions & 2 deletions .generator/src/generator/formatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import keyword
import warnings
import re

from uuid import UUID
import dateutil.parser
import m2r2

Expand Down Expand Up @@ -296,6 +296,11 @@ def format_datetime(x):
if "tzoffset" in result:
imports["dateutil.tz"].add("tzoffset")
return result

def format_uuid(x):
imports["uuid"].add("UUID")
result = repr(UUID(x))
return result

formatter = {
"double": lambda s: repr(float(s)),
Expand All @@ -305,7 +310,8 @@ def format_datetime(x):
"date-time": format_datetime,
"binary": lambda s: f'open("{s}", "rb")',
"email": repr,
None: repr,
"uuid": format_uuid,
None: repr,
}[schema.get("format")]

# TODO format date and datetime
Expand Down
4 changes: 3 additions & 1 deletion .generator/src/generator/openapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def basic_type_to_python(type_, schema, typing=False):
if type_ is None:
if typing:
return "Any"
return "bool, date, datetime, dict, float, int, list, str, none_type"
return "bool, date, datetime, dict, float, int, list, str, UUID, none_type"
if type_ == "integer":
return "int"
elif type_ == "number":
Expand All @@ -35,6 +35,8 @@ def basic_type_to_python(type_, schema, typing=False):
return "datetime"
elif format_ == "binary":
return "file_type"
elif format_ == "uuid":
return "UUID"
return "str"
elif type_ == "boolean":
return "bool"
Expand Down
1 change: 1 addition & 0 deletions .generator/src/generator/templates/api.j2
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ from {{ package }}.model_utils import (
none_type,
UnsetType,
unset,
UUID,
)
{%- for model in get_api_models(operations) %}
from {{ package }}.{{ version }}.model.{{ model|safe_snake_case }} import {{ model }}
Expand Down
3 changes: 3 additions & 0 deletions .generator/src/generator/templates/api_client.j2
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import warnings
import multiprocessing
from multiprocessing.pool import ThreadPool
from datetime import date, datetime
from uuid import UUID
import io
import os
import re
Expand Down Expand Up @@ -177,6 +178,8 @@ class ApiClient:
if getattr(obj, "tzinfo", None) is not None:
return obj.isoformat()
return "{}Z".format(obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3])
elif isinstance(obj, UUID ):
return str(obj)
elif isinstance(obj, ModelSimple):
return cls.sanitize_for_serialization(obj.value)
elif isinstance(obj, (list, tuple)):
Expand Down
1 change: 1 addition & 0 deletions .generator/src/generator/templates/model.j2
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ from {{ package }}.model_utils import (
none_type,
unset,
UnsetType,
UUID,
)

{% if "enum" in model -%}
Expand Down
27 changes: 20 additions & 7 deletions .generator/src/generator/templates/model_utils.j2
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from contextlib import suppress
from datetime import date, datetime
from uuid import UUID
import enum
import inspect
import io
Expand Down Expand Up @@ -52,7 +53,7 @@ class cached_property(object):
return result


PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, UUID, file_type)


def allows_single_value_input(cls):
Expand Down Expand Up @@ -567,7 +568,8 @@ COERCION_INDEX_BY_TYPE = {
datetime: 9,
date: 10,
str: 11,
file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type.
UUID: 12,
file_type: 13, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type.
}

# these are used to limit what type conversions we try to do
Expand All @@ -576,6 +578,7 @@ COERCION_INDEX_BY_TYPE = {
UPCONVERSION_TYPE_PAIRS = (
(str, datetime),
(str, date),
(str, UUID),
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed),
(dict, ModelComposed),
Expand Down Expand Up @@ -624,6 +627,7 @@ COERCIBLE_TYPE_PAIRS = {
# (str, float),
(str, datetime),
(str, date),
(str, UUID),
# (int, str),
# (float, str),
(str, file_type),
Expand Down Expand Up @@ -664,6 +668,8 @@ def get_simple_class(input_value):
return date
elif isinstance(input_value, str):
return str
elif isinstance(input_value, UUID):
return UUID
return type(input_value)


Expand All @@ -675,7 +681,7 @@ def check_allowed_values(allowed_values, input_variable, input_values):
:type input_variable: str
:param input_values: The values that we are checking to see if they are in
allowed_values.
:type input_values: list/str/int/float/date/datetime
:type input_values: list/str/int/float/date/datetime/uuid
"""
if isinstance(input_values, list) and not set(input_values).issubset(allowed_values):
invalid_values = (", ".join(map(str, set(input_values) - allowed_values)),)
Expand Down Expand Up @@ -721,7 +727,7 @@ def check_validations(validations, input_variable, input_values, configuration=N
:param input_variable: The name of the input variable.
:type input_variable: str
:param input_values: The values that we are checking.
:type input_values: list/str/int/float/date/datetime
:type input_values: list/str/int/float/date/datetime/uuid
:param configuration: The configuration instance.
:type configuration: Configuration
"""
Expand Down Expand Up @@ -1014,7 +1020,7 @@ def deserialize_primitive(data, klass, path_to_item):
:param klass: The class to convert to.
:type klass: str/class

:rtype: int, float, str, bool, date, datetime
:rtype: int, float, str, bool, date, datetime, UUID
"""
additional_message = ""
try:
Expand Down Expand Up @@ -1042,11 +1048,18 @@ def deserialize_primitive(data, klass, path_to_item):
raise ValueError("This is not a date")
return parse(data).date()
else:
converted_value = klass(data)
if isinstance(data, str) and klass == UUID:
try:
converted_value = UUID(data)
except ValueError:
raise ValueError("This is not an UUID")
if isinstance(data, str) and klass == float:
converted_value = float(data)
if str(converted_value) != data:
# '7' -> 7.0 -> '7.0' != '7'
raise ValueError("This is not a float")
else:
converted_value = klass(data)
return converted_value
except (OverflowError, ValueError) as ex:
# parse can raise OverflowError
Expand Down Expand Up @@ -1479,7 +1492,7 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
:type model_arg: int, float, bool, str, date, datetime, ModelSimple, None
:type model_arg: int, float, bool, str, date, datetime, ModelSimple, UUID, None
"""
if len(cls._composed_schemas["oneOf"]) == 0:
return None
Expand Down
3 changes: 3 additions & 0 deletions src/datadog_api_client/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import multiprocessing
from multiprocessing.pool import ThreadPool
from datetime import date, datetime
from uuid import UUID
import io
import os
import re
Expand Down Expand Up @@ -179,6 +180,8 @@ def sanitize_for_serialization(cls, obj):
if getattr(obj, "tzinfo", None) is not None:
return obj.isoformat()
return "{}Z".format(obj.strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3])
elif isinstance(obj, UUID):
return str(obj)
elif isinstance(obj, ModelSimple):
return cls.sanitize_for_serialization(obj.value)
elif isinstance(obj, (list, tuple)):
Expand Down
27 changes: 20 additions & 7 deletions src/datadog_api_client/model_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from contextlib import suppress
from datetime import date, datetime
from uuid import UUID
import enum
import inspect
import io
Expand Down Expand Up @@ -54,7 +55,7 @@ def __get__(self, instance, cls=None):
return result


PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, file_type)
PRIMITIVE_TYPES = (list, float, int, bool, datetime, date, str, UUID, file_type)


def allows_single_value_input(cls):
Expand Down Expand Up @@ -570,7 +571,8 @@ def __eq__(self, other):
datetime: 9,
date: 10,
str: 11,
file_type: 12, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type.
UUID: 12,
file_type: 13, # 'file_type' is an alias for the built-in 'file' or 'io.IOBase' type.
}

# these are used to limit what type conversions we try to do
Expand All @@ -579,6 +581,7 @@ def __eq__(self, other):
UPCONVERSION_TYPE_PAIRS = (
(str, datetime),
(str, date),
(str, UUID),
(int, float), # A float may be serialized as an integer, e.g. '3' is a valid serialized float.
(list, ModelComposed),
(dict, ModelComposed),
Expand Down Expand Up @@ -627,6 +630,7 @@ def __eq__(self, other):
# (str, float),
(str, datetime),
(str, date),
(str, UUID),
# (int, str),
# (float, str),
(str, file_type),
Expand Down Expand Up @@ -667,6 +671,8 @@ def get_simple_class(input_value):
return date
elif isinstance(input_value, str):
return str
elif isinstance(input_value, UUID):
return UUID
return type(input_value)


Expand All @@ -678,7 +684,7 @@ def check_allowed_values(allowed_values, input_variable, input_values):
:type input_variable: str
:param input_values: The values that we are checking to see if they are in
allowed_values.
:type input_values: list/str/int/float/date/datetime
:type input_values: list/str/int/float/date/datetime/uuid
"""
if isinstance(input_values, list) and not set(input_values).issubset(allowed_values):
invalid_values = (", ".join(map(str, set(input_values) - allowed_values)),)
Expand Down Expand Up @@ -724,7 +730,7 @@ def check_validations(validations, input_variable, input_values, configuration=N
:param input_variable: The name of the input variable.
:type input_variable: str
:param input_values: The values that we are checking.
:type input_values: list/str/int/float/date/datetime
:type input_values: list/str/int/float/date/datetime/uuid
:param configuration: The configuration instance.
:type configuration: Configuration
"""
Expand Down Expand Up @@ -1017,7 +1023,7 @@ def deserialize_primitive(data, klass, path_to_item):
:param klass: The class to convert to.
:type klass: str/class
:rtype: int, float, str, bool, date, datetime
:rtype: int, float, str, bool, date, datetime, UUID
"""
additional_message = ""
try:
Expand Down Expand Up @@ -1045,11 +1051,18 @@ def deserialize_primitive(data, klass, path_to_item):
raise ValueError("This is not a date")
return parse(data).date()
else:
converted_value = klass(data)
if isinstance(data, str) and klass == UUID:
try:
converted_value = UUID(data)
except ValueError:
raise ValueError("This is not an UUID")
if isinstance(data, str) and klass == float:
converted_value = float(data)
if str(converted_value) != data:
# '7' -> 7.0 -> '7.0' != '7'
raise ValueError("This is not a float")
else:
converted_value = klass(data)
return converted_value
except (OverflowError, ValueError) as ex:
# parse can raise OverflowError
Expand Down Expand Up @@ -1482,7 +1495,7 @@ def get_oneof_instance(cls, model_kwargs, constant_kwargs, model_arg=None):
Notes:
- this is only passed in when oneOf includes types which are not object
- None is used to suppress handling of model_arg, nullable models are handled in __new__
:type model_arg: int, float, bool, str, date, datetime, ModelSimple, None
:type model_arg: int, float, bool, str, date, datetime, ModelSimple, UUID, None
"""
if len(cls._composed_schemas["oneOf"]) == 0:
return None
Expand Down
5 changes: 3 additions & 2 deletions src/datadog_api_client/v1/model/agent_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
date,
datetime,
none_type,
UUID,
)


Expand All @@ -18,11 +19,11 @@ class AgentCheck(ModelSimple):
Array of strings.
:type value: [bool, date, datetime, dict, float, int, list, str, none_type]
:type value: [bool, date, datetime, dict, float, int, list, str, UUID, none_type]
"""

@cached_property
def openapi_types(_):
return {
"value": ([bool, date, datetime, dict, float, int, list, str, none_type],),
"value": ([bool, date, datetime, dict, float, int, list, str, UUID, none_type],),
}
Loading

0 comments on commit 5fdcab6

Please sign in to comment.