Skip to content

Commit

Permalink
Add as_names conversion for enums (#160)
Browse files Browse the repository at this point in the history
  • Loading branch information
wyfo authored Jun 13, 2021
1 parent d882be4 commit 0a3b707
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 2 deletions.
2 changes: 2 additions & 0 deletions apischema/conversions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"AnyConversion",
"Conversion",
"LazyConversion",
"as_names",
"as_str",
"dataclass_input_wrapper",
"dataclass_model",
Expand All @@ -15,6 +16,7 @@

from .conversions import AnyConversion, Conversion, LazyConversion
from .converters import (
as_names,
as_str,
deserializer,
inherited_deserializer,
Expand Down
27 changes: 26 additions & 1 deletion apischema/conversions/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
import warnings
from collections import defaultdict
from dataclasses import replace
from enum import Enum
from functools import partial
from types import new_class
from typing import (
Callable,
Dict,
Expand All @@ -22,6 +25,7 @@
resolve_conversion,
)
from apischema.conversions.utils import Converter, is_convertible
from apischema.type_names import type_name
from apischema.types import AnyType
from apischema.typing import is_type_var
from apischema.utils import (
Expand Down Expand Up @@ -244,10 +248,31 @@ def inherited_deserializer(method=None, **kwargs):
return InheritedDeserializer(method, **kwargs)


Cls = TypeVar("Cls", bound=Type)
Cls = TypeVar("Cls", bound=type)


def as_str(cls: Cls) -> Cls:
deserializer(Conversion(cls, source=str))
serializer(Conversion(str, source=cls))
return cls


EnumCls = TypeVar("EnumCls", bound=Type[Enum])


def as_names(cls: EnumCls, aliaser: Callable[[str], str] = lambda s: s) -> EnumCls:
# Enum requires to call namespace __setitem__
def exec_body(namespace: dict):
for elt in cls: # type: ignore
namespace[elt.name] = aliaser(elt.name)

if not issubclass(cls, Enum):
raise TypeError("as_names must be called with Enum subclass")
name_cls = type_name(None)(
new_class(cls.__name__, (str, Enum), exec_body=exec_body)
)
deserializer(Conversion(partial(getattr, cls), source=name_cls, target=cls))
serializer(
Conversion(lambda obj: getattr(name_cls, obj.name), source=cls, target=name_cls)
)
return cls
8 changes: 8 additions & 0 deletions docs/conversions.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ A common pattern of conversion concerns class having a string constructor and a
!!! note
Previously mentioned standard types are handled by *apischema* using `as_str`.

## Use `Enum` names

`Enum` subclasses are (de)serialized using values. However, you may want to use enumeration names instead, that's why *apischema* provides `apischema.conversion.as_names` to decorate `Enum` subclasses.

```python
{!as_names.py!}
```

## Object deserialization — transform function into a dataclass deserializer

`apischema.objects.object_deserialization` can convert a function into a new function taking a unique parameter, a dataclass whose fields are mapped from the original function parameters.
Expand Down
3 changes: 2 additions & 1 deletion docs/data_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ They correpond to JSON *object* and are serialized to `dict`.

`enum.Enum` subclasses, `typing.Literal`

For `Enum`, this is the value and not the attribute name that is serialized
!!! warning
`Enum` subclasses are (de)serialized using **values**, not names. *apischema* also provides a [conversion](conversions.md#using-enum-names) to use names instead.

#### Typing facilities

Expand Down
24 changes: 24 additions & 0 deletions examples/as_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from enum import Enum

from apischema import deserialize, serialize
from apischema.conversions import as_names
from apischema.json_schema import deserialization_schema, serialization_schema


@as_names
class MyEnum(Enum):
FOO = object()
BAR = object()


assert deserialize(MyEnum, "FOO") == MyEnum.FOO
assert serialize(MyEnum, MyEnum.FOO) == "FOO"
assert (
deserialization_schema(MyEnum)
== serialization_schema(MyEnum)
== {
"$schema": "http://json-schema.org/draft/2019-09/schema#",
"type": "string",
"enum": ["FOO", "BAR"],
}
)

0 comments on commit 0a3b707

Please sign in to comment.