You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
6.4 KiB
Python
242 lines
6.4 KiB
Python
"""Collection of functions for building custom `json_default` functions.
|
|
|
|
In general functions come in pairs of `use_x_default` and `x_default`, where the former is used
|
|
to determine if you should call the latter.
|
|
|
|
Most `use_x_default` functions also act as a [`TypeGuard`](https://mypy.readthedocs.io/en/stable/type_narrowing.html#user-defined-type-guards).
|
|
"""
|
|
|
|
### IMPORTS
|
|
### ============================================================================
|
|
## Future
|
|
from __future__ import annotations
|
|
|
|
## Standard Library
|
|
import base64
|
|
import dataclasses
|
|
import datetime
|
|
import enum
|
|
import sys
|
|
from types import TracebackType
|
|
from typing import Any
|
|
import traceback
|
|
import uuid
|
|
|
|
if sys.version_info >= (3, 10):
|
|
from typing import TypeGuard
|
|
else:
|
|
from typing_extensions import TypeGuard
|
|
|
|
## Installed
|
|
|
|
## Application
|
|
|
|
|
|
### FUNCTIONS
|
|
### ============================================================================
|
|
def unknown_default(obj: Any) -> str:
|
|
"""Backup default function for any object type.
|
|
|
|
Will attempt to use `str` or `repr`. If both functions error will return
|
|
the string `"__could_not_encode__"`.
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
try:
|
|
return str(obj)
|
|
except Exception: # pylint: disable=broad-exception-caught
|
|
pass
|
|
try:
|
|
return repr(obj)
|
|
except Exception: # pylint: disable=broad-exception-caught
|
|
pass
|
|
return "__could_not_encode__"
|
|
|
|
|
|
## Types
|
|
## -----------------------------------------------------------------------------
|
|
def use_type_default(obj: Any) -> TypeGuard[type]:
|
|
"""Default check function for `type` objects (aka classes)."""
|
|
return isinstance(obj, type)
|
|
|
|
|
|
def type_default(obj: type) -> str:
|
|
"""Default function for `type` objects.
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return obj.__name__
|
|
|
|
|
|
## Dataclasses
|
|
## -----------------------------------------------------------------------------
|
|
def use_dataclass_default(obj: Any) -> bool:
|
|
"""Default check function for dataclass instances"""
|
|
return dataclasses.is_dataclass(obj) and not isinstance(obj, type)
|
|
|
|
|
|
def dataclass_default(obj) -> dict[str, Any]:
|
|
"""Default function for dataclass instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return dataclasses.asdict(obj)
|
|
|
|
|
|
## Dates and Times
|
|
## -----------------------------------------------------------------------------
|
|
def use_time_default(obj: Any) -> TypeGuard[datetime.time]:
|
|
"""Default check function for `datetime.time` instances"""
|
|
return isinstance(obj, datetime.time)
|
|
|
|
|
|
def time_default(obj: datetime.time) -> str:
|
|
"""Default function for `datetime.time` instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return obj.isoformat()
|
|
|
|
|
|
def use_date_default(obj: Any) -> TypeGuard[datetime.date]:
|
|
"""Default check function for `datetime.date` instances"""
|
|
return isinstance(obj, datetime.date)
|
|
|
|
|
|
def date_default(obj: datetime.date) -> str:
|
|
"""Default function for `datetime.date` instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return obj.isoformat()
|
|
|
|
|
|
def use_datetime_default(obj: Any) -> TypeGuard[datetime.datetime]:
|
|
"""Default check function for `datetime.datetime` instances"""
|
|
return isinstance(obj, datetime.datetime)
|
|
|
|
|
|
def datetime_default(obj: datetime.datetime) -> str:
|
|
"""Default function for `datetime.datetime` instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return obj.isoformat()
|
|
|
|
|
|
def use_datetime_any(obj: Any) -> TypeGuard[datetime.time | datetime.date | datetime.datetime]:
|
|
"""Default check function for `datetime` related instances"""
|
|
return isinstance(obj, (datetime.time, datetime.date, datetime.datetime))
|
|
|
|
|
|
def datetime_any(obj: datetime.time | datetime.date | datetime.date) -> str:
|
|
"""Default function for `datetime` related instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return obj.isoformat()
|
|
|
|
|
|
## Exception and Tracebacks
|
|
## -----------------------------------------------------------------------------
|
|
def use_exception_default(obj: Any) -> TypeGuard[BaseException]:
|
|
"""Default check function for exception instances.
|
|
|
|
Exception classes are not treated specially and should be handled by the
|
|
`[use_]type_default` functions.
|
|
"""
|
|
return isinstance(obj, BaseException)
|
|
|
|
|
|
def exception_default(obj: BaseException) -> str:
|
|
"""Default function for exception instances
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return f"{obj.__class__.__name__}: {obj}"
|
|
|
|
|
|
def use_traceback_default(obj: Any) -> TypeGuard[TracebackType]:
|
|
"""Default check function for tracebacks"""
|
|
return isinstance(obj, TracebackType)
|
|
|
|
|
|
def traceback_default(obj: TracebackType) -> str:
|
|
"""Default function for tracebacks
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return "".join(traceback.format_tb(obj)).strip()
|
|
|
|
|
|
## Enums
|
|
## -----------------------------------------------------------------------------
|
|
def use_enum_default(obj: Any) -> TypeGuard[enum.Enum | enum.EnumMeta]:
|
|
"""Default check function for enums.
|
|
|
|
Supports both enum classes and enum values.
|
|
"""
|
|
return isinstance(obj, (enum.Enum, enum.EnumMeta))
|
|
|
|
|
|
def enum_default(obj: enum.Enum | enum.EnumMeta) -> Any | list[Any]:
|
|
"""Default function for enums.
|
|
|
|
Supports both enum classes and enum values.
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
if isinstance(obj, enum.Enum):
|
|
return obj.value
|
|
return [e.value for e in obj] # type: ignore[var-annotated]
|
|
|
|
|
|
## UUIDs
|
|
## -----------------------------------------------------------------------------
|
|
def use_uuid_default(obj: Any) -> TypeGuard[uuid.UUID]:
|
|
"""Default check function for `uuid.UUID` instances"""
|
|
return isinstance(obj, uuid.UUID)
|
|
|
|
|
|
def uuid_default(obj: uuid.UUID) -> str:
|
|
"""Default function for `uuid.UUID` instances
|
|
|
|
Formats the UUID using "hyphen" format.
|
|
|
|
Args:
|
|
obj: object to handle
|
|
"""
|
|
return str(obj)
|
|
|
|
|
|
## Bytes
|
|
## -----------------------------------------------------------------------------
|
|
def use_bytes_default(obj: Any) -> TypeGuard[bytes | bytearray]:
|
|
"""Default check function for bytes"""
|
|
return isinstance(obj, (bytes, bytearray))
|
|
|
|
|
|
def bytes_default(obj: bytes | bytearray, url_safe: bool = True) -> str:
|
|
"""Default function for bytes
|
|
|
|
Args:
|
|
obj: object to handle
|
|
url_safe: use URL safe base 64 character set.
|
|
|
|
Returns:
|
|
The byte data as a base 64 string.
|
|
"""
|
|
if url_safe:
|
|
return base64.urlsafe_b64encode(obj).decode("utf8")
|
|
return base64.b64encode(obj).decode("utf8")
|