Module xmodel.converters
Expand source code
from uuid import UUID
import ciso8601
from xmodel.base.fields import Converter, Field
import datetime as dt
from typing import Union, TYPE_CHECKING, Type, TypeVar, Generic, Dict, Any
from xsentinels.null import Null, NullType, Nullable
from xmodel.errors import XModelError
from decimal import Decimal
from xbool import bool_value
_to_obj_directions = {Converter.Direction.from_json, Converter.Direction.to_model}
Direction = Converter.Direction
T = TypeVar("T")
if TYPE_CHECKING:
# Allows IDE to get type reference without a circular import issue.
from xmodel import BaseApi
def to_datetime(value):
# Should pretty much always be a string, so check for that first.
if isinstance(value, str):
return ciso8601.parse_datetime(value)
if isinstance(value, dt.datetime):
return value
if isinstance(value, dt.date):
return dt.datetime(value.year, value.month, value.day, tzinfo=dt.timezone.utc)
raise ValueError(
f"Tried to convert a datetime from unsupported BaseSettings value ({value})."
)
def convert_json_int(
api: "BaseApi",
direction: Direction,
field: Field,
value: Union[dt.date, str, None]
) -> int:
return int(value)
class EnumConverter(Converter):
def from_json(self, api: 'BaseApi', field: 'Field', value: Any):
# todo: lists of enums, someday...
if value in (Null, None):
return value
if isinstance(value, field.type_hint):
return value
return field.type_hint(value)
def to_json(self, api: 'BaseApi', field: 'Field', value: Any):
if value in (Null, None):
return value
return value.value
def to_model(self, *args):
return self.from_json(*args)
class ConvertBasicType(Converter, Generic[T]):
"""
Implements the `xmodel.fields.Converter` converter field interface for a basic type.
Whatever type you pass into my init method will be called when a type needs to be converted.
As an example, say we had `ConvertBasicType(basic_type=int)` as a converter we are using.
When this converter is passed a string to conver, such as `"1"` the `ConvertBasicType`
object would just do this for you:
>>> int("1")
If the type is exactly the same, no conversion happens and the value is passed back unchanged.
ie:
>>> value: str
>>> self: ConvertBasicType[str]
>>>
>>> if type(value) is self.basic_type:
... return value
"""
basic_type: Type[T]
def __init__(self, basic_type: Type[T]):
"""
Args:
basic_type: Type to auto-convert to, in a basic fashion.
See `ConvertBasicType` for more details.
"""
self.basic_type = basic_type
def convert_basic_value(self, value) -> Nullable[T]:
if value is Null:
return Null
basic_type = self.basic_type
if type(value) is basic_type:
return value
return basic_type(value)
def __call__(
self,
api: "BaseApi",
direction: Direction,
field: Field,
value: Union[dt.date, str, None]
) -> T:
if value in (None, Null) and direction == Direction.from_json and field.nullable:
return Null
if value is None:
return None
if isinstance(value, list):
return [self.convert_basic_value(x) for x in value]
return self.convert_basic_value(value)
class ConvertBasicBool(ConvertBasicType[bool]):
def convert_basic_value(self, value) -> T:
if isinstance(value, str):
return bool_value(value)
return super().convert_basic_value(value)
def __init__(self):
super().__init__(basic_type=bool)
class ConvertBasicInt(ConvertBasicType[int]):
def __init__(self):
super().__init__(basic_type=int)
def convert_basic_value(self, value) -> T:
# Convert a blank-string into a zero-int.
if isinstance(value, str) and not value:
return 0
# Otherwise do the normal thing...
return super().convert_basic_value(value)
def convert_json_date(
api: "BaseApi",
direction: Converter.Direction,
field: Field,
value: Union[dt.date, str, None]
) -> Union[dt.date, str, None]:
""" Default converter method used for converting date to/from json.
See `xmodel.fields.Converter` for more details.
"""
if value in (None, Null) and direction == Direction.from_json and field.nullable:
return Null
if value is None or value is Null:
return value
if direction in _to_obj_directions:
return dt.datetime.strptime(value, '%Y-%m-%d').date()
if isinstance(value, dt.datetime):
value = value.date()
return value.isoformat()
def convert_json_datetime(
api: "BaseApi",
direction: Direction,
field: Field,
value: Union[dt.datetime, str, None, NullType],
as_time_zone: dt.datetime.tzinfo = dt.timezone.utc
) -> Union[dt.datetime, str, None]:
""" Default converter method used for converting datetime to/from json.
See `xmodel.fields.Converter` for more details.
"""
if value in (None, Null) and direction == Direction.from_json and field.nullable:
return Null
if value is None or value is Null:
return value
if direction in _to_obj_directions:
# Should pretty much always be a string, so check for that first.
if isinstance(value, str):
return to_datetime(value)
if isinstance(value, dt.datetime):
return value
if isinstance(value, dt.date):
return to_datetime(value)
raise XModelError(
f"Tried to convert a datetime from-json from an unknown value ({value})."
)
if isinstance(value, dt.date) and not isinstance(value, dt.datetime):
value = to_datetime(value)
if isinstance(value, str):
if not value:
return Null
value = to_datetime(value)
if not isinstance(value, dt.datetime):
raise XModelError(f"Tried to convert to json a none date/datetime value ({value}).")
# Always use UTC format.
return value.astimezone(as_time_zone).isoformat()
def convert_decimal(
api,
direction: Direction,
field: Field,
value: Any,
):
if value is None or value is Null:
return value
if direction is Direction.to_json:
if isinstance(value, Decimal):
# Prevents using exponents, ie: '1.234E-5'
return "{0:f}".format(value)
# If we don't have a decimal, try our best to get string value.
return str(value)
if direction not in (Direction.to_model, Direction.from_json):
# We don't know the direction (new direct?)
raise XModelError(
f"Unknown direction ({direction}), can't convert value ({value}); "
f"is this a new direction I need to handle?"
)
# Going into model, return a Decimal.
# Decimal class
if isinstance(value, float):
# If we don't convert to string first, we could end up with an
# undesirable binaryFloat --> Decimal conversion.
# Converting it to a string first seems to preserve the original non-binary meaning better.
value = str(value)
return Decimal(value)
def convert_uuid(
api,
direction: Direction,
field: Field,
value: Any,
):
if value is None or value is Null:
return value
if direction is Direction.to_json:
# Convert UUID object into a str.
return str(value)
if direction not in (Direction.to_model, Direction.from_json):
# We don't know the direction (new direct?)
raise XModelError(
f"Unknown direction ({direction}), can't convert value ({value}); "
f"is this a new direction I need to handle?"
)
# Going into model, return a UUID.
if isinstance(value, UUID):
return value
return UUID(value)
DEFAULT_CONVERTERS: Dict[Type, Converter] = {
Decimal: convert_decimal,
dt.date: convert_json_date,
dt.datetime: convert_json_datetime,
int: ConvertBasicInt(),
float: ConvertBasicType(basic_type=float),
str: ConvertBasicType(basic_type=str),
bool: ConvertBasicBool(),
UUID: convert_uuid,
}
Functions
def convert_decimal(api, direction: Converter.Direction, field: Field, value: Any)
-
Expand source code
def convert_decimal( api, direction: Direction, field: Field, value: Any, ): if value is None or value is Null: return value if direction is Direction.to_json: if isinstance(value, Decimal): # Prevents using exponents, ie: '1.234E-5' return "{0:f}".format(value) # If we don't have a decimal, try our best to get string value. return str(value) if direction not in (Direction.to_model, Direction.from_json): # We don't know the direction (new direct?) raise XModelError( f"Unknown direction ({direction}), can't convert value ({value}); " f"is this a new direction I need to handle?" ) # Going into model, return a Decimal. # Decimal class if isinstance(value, float): # If we don't convert to string first, we could end up with an # undesirable binaryFloat --> Decimal conversion. # Converting it to a string first seems to preserve the original non-binary meaning better. value = str(value) return Decimal(value)
def convert_json_date(api: BaseApi, direction: Converter.Direction, field: Field, value: Union[datetime.date, str, ForwardRef(None)]) ‑> Union[datetime.date, str, ForwardRef(None)]
-
Default converter method used for converting date to/from json. See
xmodel.fields.Converter
for more details.Expand source code
def convert_json_date( api: "BaseApi", direction: Converter.Direction, field: Field, value: Union[dt.date, str, None] ) -> Union[dt.date, str, None]: """ Default converter method used for converting date to/from json. See `xmodel.fields.Converter` for more details. """ if value in (None, Null) and direction == Direction.from_json and field.nullable: return Null if value is None or value is Null: return value if direction in _to_obj_directions: return dt.datetime.strptime(value, '%Y-%m-%d').date() if isinstance(value, dt.datetime): value = value.date() return value.isoformat()
def convert_json_datetime(api: BaseApi, direction: Converter.Direction, field: Field, value: Union[datetime.datetime, str, ForwardRef(None), NullType], as_time_zone:
= datetime.timezone.utc) ‑> Union[datetime.datetime, str, ForwardRef(None)] -
Default converter method used for converting datetime to/from json. See
xmodel.fields.Converter
for more details.Expand source code
def convert_json_datetime( api: "BaseApi", direction: Direction, field: Field, value: Union[dt.datetime, str, None, NullType], as_time_zone: dt.datetime.tzinfo = dt.timezone.utc ) -> Union[dt.datetime, str, None]: """ Default converter method used for converting datetime to/from json. See `xmodel.fields.Converter` for more details. """ if value in (None, Null) and direction == Direction.from_json and field.nullable: return Null if value is None or value is Null: return value if direction in _to_obj_directions: # Should pretty much always be a string, so check for that first. if isinstance(value, str): return to_datetime(value) if isinstance(value, dt.datetime): return value if isinstance(value, dt.date): return to_datetime(value) raise XModelError( f"Tried to convert a datetime from-json from an unknown value ({value})." ) if isinstance(value, dt.date) and not isinstance(value, dt.datetime): value = to_datetime(value) if isinstance(value, str): if not value: return Null value = to_datetime(value) if not isinstance(value, dt.datetime): raise XModelError(f"Tried to convert to json a none date/datetime value ({value}).") # Always use UTC format. return value.astimezone(as_time_zone).isoformat()
def convert_json_int(api: BaseApi, direction: Converter.Direction, field: Field, value: Union[datetime.date, str, ForwardRef(None)]) ‑> int
-
Expand source code
def convert_json_int( api: "BaseApi", direction: Direction, field: Field, value: Union[dt.date, str, None] ) -> int: return int(value)
def convert_uuid(api, direction: Converter.Direction, field: Field, value: Any)
-
Expand source code
def convert_uuid( api, direction: Direction, field: Field, value: Any, ): if value is None or value is Null: return value if direction is Direction.to_json: # Convert UUID object into a str. return str(value) if direction not in (Direction.to_model, Direction.from_json): # We don't know the direction (new direct?) raise XModelError( f"Unknown direction ({direction}), can't convert value ({value}); " f"is this a new direction I need to handle?" ) # Going into model, return a UUID. if isinstance(value, UUID): return value return UUID(value)
def to_datetime(value)
-
Expand source code
def to_datetime(value): # Should pretty much always be a string, so check for that first. if isinstance(value, str): return ciso8601.parse_datetime(value) if isinstance(value, dt.datetime): return value if isinstance(value, dt.date): return dt.datetime(value.year, value.month, value.day, tzinfo=dt.timezone.utc) raise ValueError( f"Tried to convert a datetime from unsupported BaseSettings value ({value})." )
Classes
class ConvertBasicBool
-
Implements the
xmodel.fields.Converter
converter field interface for a basic type.Whatever type you pass into my init method will be called when a type needs to be converted.
As an example, say we had
ConvertBasicType(basic_type=int)
as a converter we are using. When this converter is passed a string to conver, such as"1"
theConvertBasicType
object would just do this for you:>>> int("1")
If the type is exactly the same, no conversion happens and the value is passed back unchanged. ie:
>>> value: str >>> self: ConvertBasicType[str] >>> >>> if type(value) is self.basic_type: ... return value
Args
basic_type
- Type to auto-convert to, in a basic fashion.
See
ConvertBasicType
for more details.
Expand source code
class ConvertBasicBool(ConvertBasicType[bool]): def convert_basic_value(self, value) -> T: if isinstance(value, str): return bool_value(value) return super().convert_basic_value(value) def __init__(self): super().__init__(basic_type=bool)
Ancestors
- ConvertBasicType
- Converter
- typing.Generic
Class variables
var Direction
-
Inherited from:
ConvertBasicType
.Direction
Possible values for field option keys.
Methods
def __call__(self, api: BaseApi, direction: Converter.Direction, field: Field, value: Any) ‑> Any
-
Inherited from:
ConvertBasicType
.__call__
Gets called when something needs to be converted …
def convert_basic_value(self, value) ‑> ~T
-
Expand source code
def convert_basic_value(self, value) -> T: if isinstance(value, str): return bool_value(value) return super().convert_basic_value(value)
def from_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.from_json
todo
def to_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.to_json
todo
def to_model(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.to_model
todo
class ConvertBasicInt
-
Implements the
xmodel.fields.Converter
converter field interface for a basic type.Whatever type you pass into my init method will be called when a type needs to be converted.
As an example, say we had
ConvertBasicType(basic_type=int)
as a converter we are using. When this converter is passed a string to conver, such as"1"
theConvertBasicType
object would just do this for you:>>> int("1")
If the type is exactly the same, no conversion happens and the value is passed back unchanged. ie:
>>> value: str >>> self: ConvertBasicType[str] >>> >>> if type(value) is self.basic_type: ... return value
Args
basic_type
- Type to auto-convert to, in a basic fashion.
See
ConvertBasicType
for more details.
Expand source code
class ConvertBasicInt(ConvertBasicType[int]): def __init__(self): super().__init__(basic_type=int) def convert_basic_value(self, value) -> T: # Convert a blank-string into a zero-int. if isinstance(value, str) and not value: return 0 # Otherwise do the normal thing... return super().convert_basic_value(value)
Ancestors
- ConvertBasicType
- Converter
- typing.Generic
Class variables
var Direction
-
Inherited from:
ConvertBasicType
.Direction
Possible values for field option keys.
Methods
def __call__(self, api: BaseApi, direction: Converter.Direction, field: Field, value: Any) ‑> Any
-
Inherited from:
ConvertBasicType
.__call__
Gets called when something needs to be converted …
def convert_basic_value(self, value) ‑> ~T
-
Expand source code
def convert_basic_value(self, value) -> T: # Convert a blank-string into a zero-int. if isinstance(value, str) and not value: return 0 # Otherwise do the normal thing... return super().convert_basic_value(value)
def from_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.from_json
todo
def to_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.to_json
todo
def to_model(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
ConvertBasicType
.to_model
todo
class ConvertBasicType (basic_type: Type[~T])
-
Implements the
xmodel.fields.Converter
converter field interface for a basic type.Whatever type you pass into my init method will be called when a type needs to be converted.
As an example, say we had
ConvertBasicType(basic_type=int)
as a converter we are using. When this converter is passed a string to conver, such as"1"
theConvertBasicType
object would just do this for you:>>> int("1")
If the type is exactly the same, no conversion happens and the value is passed back unchanged. ie:
>>> value: str >>> self: ConvertBasicType[str] >>> >>> if type(value) is self.basic_type: ... return value
Args
basic_type
- Type to auto-convert to, in a basic fashion.
See
ConvertBasicType
for more details.
Expand source code
class ConvertBasicType(Converter, Generic[T]): """ Implements the `xmodel.fields.Converter` converter field interface for a basic type. Whatever type you pass into my init method will be called when a type needs to be converted. As an example, say we had `ConvertBasicType(basic_type=int)` as a converter we are using. When this converter is passed a string to conver, such as `"1"` the `ConvertBasicType` object would just do this for you: >>> int("1") If the type is exactly the same, no conversion happens and the value is passed back unchanged. ie: >>> value: str >>> self: ConvertBasicType[str] >>> >>> if type(value) is self.basic_type: ... return value """ basic_type: Type[T] def __init__(self, basic_type: Type[T]): """ Args: basic_type: Type to auto-convert to, in a basic fashion. See `ConvertBasicType` for more details. """ self.basic_type = basic_type def convert_basic_value(self, value) -> Nullable[T]: if value is Null: return Null basic_type = self.basic_type if type(value) is basic_type: return value return basic_type(value) def __call__( self, api: "BaseApi", direction: Direction, field: Field, value: Union[dt.date, str, None] ) -> T: if value in (None, Null) and direction == Direction.from_json and field.nullable: return Null if value is None: return None if isinstance(value, list): return [self.convert_basic_value(x) for x in value] return self.convert_basic_value(value)
Ancestors
- Converter
- typing.Generic
Subclasses
Class variables
var Direction
-
Inherited from:
Converter
.Direction
Possible values for field option keys.
var basic_type : Type[~T]
Methods
def __call__(self, api: BaseApi, direction: Converter.Direction, field: Field, value: Any) ‑> Any
-
Inherited from:
Converter
.__call__
Gets called when something needs to be converted …
def convert_basic_value(self, value) ‑> Union[~T, NullType]
-
Expand source code
def convert_basic_value(self, value) -> Nullable[T]: if value is Null: return Null basic_type = self.basic_type if type(value) is basic_type: return value return basic_type(value)
def from_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
Converter
.from_json
todo
def to_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
Converter
.to_json
todo
def to_model(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
Converter
.to_model
todo
class EnumConverter
-
This is meant to be a Callable that converts to/from a type when a value is assigned to a
BaseModel
.See
Converter.__call__
for the calling interface.You can set these on
Field.converter
orBaseApi.default_converters
.Expand source code
class EnumConverter(Converter): def from_json(self, api: 'BaseApi', field: 'Field', value: Any): # todo: lists of enums, someday... if value in (Null, None): return value if isinstance(value, field.type_hint): return value return field.type_hint(value) def to_json(self, api: 'BaseApi', field: 'Field', value: Any): if value in (Null, None): return value return value.value def to_model(self, *args): return self.from_json(*args)
Ancestors
Class variables
Methods
def __call__(self, api: BaseApi, direction: Converter.Direction, field: Field, value: Any) ‑> Any
-
Inherited from:
Converter
.__call__
Gets called when something needs to be converted …
def from_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
Converter
.from_json
todo
Expand source code
def from_json(self, api: 'BaseApi', field: 'Field', value: Any): # todo: lists of enums, someday... if value in (Null, None): return value if isinstance(value, field.type_hint): return value return field.type_hint(value)
def to_json(self, api: BaseApi, field: Field, value: Any)
-
Inherited from:
Converter
.to_json
todo
Expand source code
def to_json(self, api: 'BaseApi', field: 'Field', value: Any): if value in (Null, None): return value return value.value
def to_model(self, *args)
-
Inherited from:
Converter
.to_model
todo
Expand source code
def to_model(self, *args): return self.from_json(*args)