-
Notifications
You must be signed in to change notification settings - Fork 34
Expand file tree
/
Copy pathconverter.py
More file actions
64 lines (47 loc) · 2.11 KB
/
converter.py
File metadata and controls
64 lines (47 loc) · 2.11 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
"""Centralized cattrs converter for structuring Overkiz API responses."""
from __future__ import annotations
import types
from enum import Enum
from typing import Any, Union, get_args, get_origin
import attr
import cattrs
from pyoverkiz.models import (
CommandDefinition,
CommandDefinitions,
State,
States,
)
def _is_primitive_union(t: Any) -> bool:
"""True for unions of JSON-native types (e.g. StateType).
Excludes unions containing attrs classes (e.g. Definition | None) since those
need actual structuring by cattrs.
"""
origin = get_origin(t)
if origin is not Union and not isinstance(t, types.UnionType):
return False
non_none = [arg for arg in get_args(t) if arg is not type(None)]
if any(isinstance(arg, type) and attr.has(arg) for arg in non_none):
return False
# Exclude pure Optional[Enum] unions — those need the Enum structure hook.
return not all(isinstance(arg, type) and issubclass(arg, Enum) for arg in non_none)
def _make_converter() -> cattrs.Converter:
c = cattrs.Converter()
# JSON-native unions like StateType (str | int | float | … | None) are already the
# correct Python type after JSON parsing — tell cattrs to pass them through as-is.
c.register_structure_hook_func(_is_primitive_union, lambda v, _: v)
# Enums: call the constructor so UnknownEnumMixin._missing_ can handle unknown values
c.register_structure_hook_func(
lambda t: isinstance(t, type) and issubclass(t, Enum),
lambda v, t: v if isinstance(v, t) else t(v),
)
# Custom container types that take a list in __init__
def _structure_states(val: Any, _: type) -> States:
if val is None:
return States()
return States([c.structure(s, State) for s in val])
def _structure_command_definitions(val: Any, _: type) -> CommandDefinitions:
return CommandDefinitions([c.structure(cd, CommandDefinition) for cd in val])
c.register_structure_hook(States, _structure_states)
c.register_structure_hook(CommandDefinitions, _structure_command_definitions)
return c
converter = _make_converter()