1+ import base64
12import copy
23import json
3- from dataclasses import fields , is_dataclass , asdict
4+ from abc import ABC , abstractmethod
5+ from dataclasses import asdict , fields , is_dataclass
6+ from datetime import date , datetime , time
47from enum import Enum
58from typing import Any , List , Tuple
9+ from uuid import UUID
610
711import yaml
812from essentials .json import FriendlyEncoder
@@ -21,6 +25,38 @@ class OpenAPIRoot(OpenAPIElement):
2125 """Base class for a root OpenAPI Documentation"""
2226
2327
28+ class ValueTypeHandler (ABC ):
29+ @abstractmethod
30+ def normalize (self , value : Any ) -> Any :
31+ """Normalizes a value of the given type into another type."""
32+
33+
34+ class CommonBuiltInTypesHandler (ValueTypeHandler ):
35+ def normalize (self , value : Any ) -> Any :
36+ if isinstance (value , UUID ):
37+ return str (value )
38+
39+ if isinstance (value , Enum ):
40+ return value .value
41+
42+ if isinstance (value , time ):
43+ return value .strftime ("%H:%M:%S" )
44+
45+ if isinstance (value , datetime ):
46+ return value .isoformat ()
47+
48+ if isinstance (value , date ):
49+ return value .strftime ("%Y-%m-%d" )
50+
51+ if isinstance (value , bytes ):
52+ return base64 .urlsafe_b64encode (value ).decode ("utf8" )
53+
54+ return value
55+
56+
57+ TYPES_HANDLERS = [CommonBuiltInTypesHandler ()]
58+
59+
2460def normalize_key (key : Any ) -> str :
2561 if isinstance (key , Enum ):
2662 return key .value
@@ -43,12 +79,23 @@ def normalize_dict_factory(items: List[Tuple[Any, Any]]) -> Any:
4379 data ["$ref" ] = value
4480 continue
4581
46- if isinstance (value , Enum ):
47- value = value .value
82+ for handler in TYPES_HANDLERS :
83+ value = handler .normalize (value )
84+
4885 data [normalize_key (key )] = value
4986 return data
5087
5188
89+ def regular_dict_factory (items : List [Tuple [Any , Any ]]) -> Any :
90+ data = {}
91+ for key , value in items :
92+ for handler in TYPES_HANDLERS :
93+ value = handler .normalize (value )
94+
95+ data [key ] = value
96+ return data
97+
98+
5299# replicates the asdict method from dataclasses module, to support
53100# bypassing "asdict" on child properties when they implement a `to_obj`
54101# method: some entities require a specific shape when represented
@@ -62,7 +109,7 @@ def _asdict_inner(obj, dict_factory):
62109 result .append ((f .name , value ))
63110 return dict_factory (result )
64111 if is_dataclass (obj ):
65- return asdict (obj )
112+ return asdict (obj , dict_factory = regular_dict_factory )
66113 elif isinstance (obj , (list , tuple )):
67114 return type (obj )(_asdict_inner (v , dict_factory ) for v in obj )
68115 elif isinstance (obj , dict ):
@@ -79,7 +126,7 @@ def normalize_dict(obj):
79126 return obj .to_obj ()
80127 if isinstance (obj , OpenAPIElement ):
81128 return _asdict_inner (obj , dict_factory = normalize_dict_factory )
82- return asdict (obj )
129+ return asdict (obj , dict_factory = regular_dict_factory )
83130
84131
85132class Serializer :
0 commit comments