Utilities

API package path: mcdreforged.api.utils

mcdreforged.utils.serializer.serialize(obj: Any) None | int | float | str | bool | list | dict[source]

A utility function to serialize any object into a json-like python object. Here, being json-like means that the return value can be passed to e.g. json.dumps() directly

Serialization rules:

  • Immutable object, including None, int, float, str and bool, will be directly returned

  • list and tuple will be serialized into a list will all the items serialized

  • dict will be converted into a dict will all the keys and values serialized

  • re.Pattern will be converted to a str, with the value of re.Pattern.pattern. Notes: if re.Pattern.pattern returns bytes, it will be decoded into a utf8 str

  • Normal object will be converted to a dict with all of its public fields. The keys are the name of the fields and the values are the serialized field values

New in version v2.8.0: If the object is a Serializable, the value field order will follow the order in the annotation

New in version v2.12.0: Added custom subclass of base classes and re.Pattern support

Parameters:

obj – The object to be serialized

Returns:

The serialized result

mcdreforged.utils.serializer.deserialize(data: Any, cls: Type[T], *, error_at_missing: bool = False, error_at_redundancy: bool = False, missing_callback: Callable[[Any, Type, str], Any] | None = None, redundancy_callback: Callable[[Any, Type, str, Any], Any] | None = None) T[source]

A utility function to deserialize a json-like object into an object in given class

If the target class contains nested items / fields, corresponding detailed type annotations are required. The items / fields will be deserialized recursively

Supported target classes:

  • Immutable object: None, bool, int, float and str

    • The class of the input data should equal to target class. No implicit type conversion will happen

    • As an exception, float also accepts an int as the input data

  • Standard container: list, dict. Type of its content should be type annotated

    • typing.List, list: Target class needs to be e.g. List[int] or list[int] (python 3.9+)

    • typing.Dict, dict: Target class needs to be e.g. Dict[str, bool] or dict[str, bool] (python 3.9+)

  • Custom subclass of following base classes: int, float, str, bool, list and dict

  • Types in the typing module:

    • typing.Union: Iterate through its available candidate classes, and return the first successful deserialization result

    • typing.Optional: Actually it will be converted to a typing.Union automatically

    • typing.Any: The input data will be directed returned as the result

    • typing.Literal: The input data needs to be in parameter the of Literal, then the input data will be returned as the result

  • Regular expression pattern (re.Pattern). The input data should be a str

  • Normal class: The class should have its fields type annotated. It’s constructor should accept 0 input parameter. Example class:

    class MyClass:
        some_str: str
        a_list: List[int]
    

    The input data needs to be a dict. Keys and values in the dict correspond to the field names and serialized field values. Example dict:

    {'some_str': 'foo', 'a_list': [1, 2, 3]}
    

    Fields are set via __setattr__, non-public fields will be ignored.

Parameters:
  • data – The json-like object to be deserialized

  • cls – The target class of the generated object

Keyword Arguments:
  • error_at_missing – A flag indicating if an exception should be risen if there are any not-assigned fields when deserializing an object. Default false

  • error_at_redundancy – A flag indicating if an exception should be risen if there are any unknown input attributes when deserializing an object. Default false

  • missing_callback – A callback function that will be invoked if there’s a not-assigned field when deserializing an object. The callback accepts 3 arguments: the data and the cls arguments from this function, and the name of the missing field

  • redundancy_callback – A callback function that will be invoked if there’s an unknown input attribute when deserializing an object. The callback accepts 4 arguments: the data and the cls arguments from this function, and the name and value of the redundancy key-value pair from the dict data

Raises:
  • TypeError – If input data doesn’t match target class, or target class is unsupported

  • ValueError – If input data is invalid, including Literal mismatch and those error flag in kwargs taking effect

Returns:

An object in class cls

New in version v2.7.0: Added typing.Literal support

New in version v2.12.0: Added custom subclass of base classes and re.Pattern support

class mcdreforged.utils.serializer.Serializable(**kwargs)[source]

An abstract class for easy serializing / deserializing

Inherit it and declare the fields of your class with type annotations, that’s all you need to do

Example:

>>> class MyData(Serializable):
...     name: str
...     values: List[int]

>>> data = MyData.deserialize({'name': 'abc', 'values': [1, 2]})
>>> print(data.name, data.values)
abc [1, 2]

>>> data.serialize()
{'name': 'abc', 'values': [1, 2]}

>>> data = MyData(name='cde')
>>> data.serialize()
{'name': 'cde'}

Serializable class nesting is also supported:

class MyStorage(Serializable):
    id: str
    best: MyData
    data: Dict[str, MyData]

You can also declare default value when declaring type annotations, then during deserializing, if the value is missing, a copy.copy() of the default value will be assigned

>>> class MyArray(Serializable):
...     array: List[int] = [0]

>>> a = MyArray(array=[1])
>>> print(a.array)
[1]
>>> b, c = MyArray.deserialize({}), MyArray.deserialize({})
>>> print(b.array)
[0]

>>> b.array == c.array == MyArray.array
True
>>> b.array is not c.array is not MyArray.array
True

Enum class will be serialized into its member name:

>>> class Gender(Enum):
...     male = 'man'
...     female = 'woman'

>>> class Person(Serializable):
...     name: str = 'zhang_san'
...     gender: Gender = Gender.male

>>> data = Person.get_default()
>>> data.serialize()
{'name': 'zhang_san', 'gender': 'male'}
>>> data.gender = Gender.female
>>> data.serialize()
{'name': 'zhang_san', 'gender': 'female'}
>>> Person.deserialize({'name': 'li_si', 'gender': 'female'}).gender == Gender.female
True
__init__(**kwargs)[source]

Create a Serializable object with given field values

Unspecified public fields with default value in the type annotation will be set to a copy (copy.copy()) of the default value

Parameters:

kwargs – A dict storing to-be-set values of its fields. It’s keys are field names and values are field values

classmethod get_field_annotations() Dict[str, Type][source]

A helper method to extract field annotations of the class

Only public fields will be extracted. Protected and private fields will be ignored

The return value will be cached for reuse

Returns:

A dict of the field annotation of the class. The keys are the field name, and the values are the type annotation of the field

New in version v2.8.0.

serialize() dict[source]

Serialize itself into a dict via function serialize()

classmethod deserialize(data: dict, **kwargs) Self[source]

Deserialize a dict into an object of this class via function deserialize()

When there are missing fields, automatically copy the class’s default value if possible. See __init__() for more details

classmethod get_default() Self[source]

Create an object of this class with default values

Actually it just invokes the constructor of the class with 0 argument

merge_from(other: Self)[source]

Merge attributes from another instance into the current one

Note

It won’t create copies of the attribute values being merged

If you want the merged values to be independent, you can make a deep copy of the other object first, and then merge from the copy

Parameters:

other – The other object to merge attributes from

New in version v2.9.0.

copy(*, deep: bool = True) Self[source]

Make a copy of the object. Only fields declared in the class annotation will be copied

By default, a deep copy will be made

Keyword Arguments:

deep – If this operation make a deep copy. True: deep copy, False: shallow copy

New in version v2.8.0.

New in version v2.9.0: Added deep keyword argument

validate_attribute(attr_name: str, attr_value: Any, **kwargs)[source]

A method that will be invoked before setting value to an attribute during the deserialization

You can validate the to-be-set attribute value in this method, and raise some ValueError for bad values

Parameters:
  • attr_name – The name of the attribute to be set

  • attr_value – The value of the attribute to be set

Keyword Arguments:

kwargs – Placeholder

Raises:

ValueError – If the validation failed

New in version v2.8.0.

on_deserialization(**kwargs)[source]

Invoked after being fully deserialized

You can do some custom initialization here

Keyword Arguments:

kwargs – Placeholder

New in version v2.8.0.