def aws_to_py()

in hasher-matcher-actioner/hmalib/common/aws_dataclass.py [0:0]


def aws_to_py(in_type: t.Type[T], aws_field: t.Any) -> T:
    """
    Convert an AWS item back into its py equivalent

    This might not even be strictly required, but we check that
    all the types are roughly what we expect, and convert
    Decimals back into ints/floats
    """
    origin = t.get_origin(in_type)
    args = t.get_args(in_type)

    check_type = origin
    if in_type is float:
        check_type = Decimal
    elif in_type is int:
        check_type = (int, Decimal)
    elif is_dataclass(in_type):
        check_type = dict
    elif check_type is set and args:
        if args[0] not in (str, float, int, Decimal):
            check_type = list

    if not isinstance(aws_field, check_type or in_type):
        # If you are getting random deserialization errors in tests that you did
        # not touch, have a look at
        # https://github.com/facebook/ThreatExchange/issues/697
        raise AWSSerializationFailure(
            "Deserialization error: "
            f"Expected {in_type} got {type(aws_field)} ({aws_field!r})"
        )

    if in_type is int:  # N
        return int(aws_field)  # type: ignore # mypy/issues/10003
    if in_type is float:  # N
        return float(aws_field)  # type: ignore # mypy/issues/10003
    if in_type is Decimal:  # N
        return aws_field  # type: ignore # mypy/issues/10003
    if in_type is str:  # S
        return aws_field  # type: ignore # mypy/issues/10003
    if in_type is bool:  # BOOL
        return aws_field  # type: ignore # mypy/issues/10003
    if in_type is t.Set[str]:  # SS
        return aws_field  # type: ignore # mypy/issues/10003
    if in_type is t.Set[int]:  # SN
        return {int(s) for s in aws_field}  # type: ignore # mypy/issues/10003
    if in_type is t.Set[float]:  # SN
        return {float(s) for s in aws_field}  # type: ignore # mypy/issues/10003

    if origin is set:  # L - special case
        return {aws_to_py(args[0], v) for v in aws_field}  # type: ignore # mypy/issues/10003
    if origin is list:  # L
        return [aws_to_py(args[0], v) for v in aws_field]  # type: ignore # mypy/issues/10003
    # It would be possible to add support for nested dataclasses here, which
    # just become maps with the keys as their attributes
    # Another option would be adding a new class that adds methods to convert
    # to an AWS-friendly struct and back
    if origin is dict and args[0] is str:  # M
        # check if value type of map origin is explicitly set
        return {k: aws_to_py(args[1], v) for k, v in aws_field.items()}  # type: ignore # mypy/issues/10003
    if is_dataclass(in_type):
        kwargs = {}
        for field in fields(in_type):
            if not field.init:
                continue
            val = aws_field.get(field.name)
            if val is None:
                continue  # Hopefully missing b/c default or version difference
            kwargs[field.name] = aws_to_py(field.type, val)
        return in_type(**kwargs)  # type: ignore  # No idea how to correctly type this

    raise AWSSerializationFailure(f"Missing deserialization logic for {in_type!r}")