python-attrs / cattrs

Composable custom class converters for attrs, dataclasses and friends.
https://catt.rs
MIT License
809 stars 113 forks source link

When structuring, how to forbid type casting to base classes? #469

Open philip-bl opened 9 months ago

philip-bl commented 9 months ago

I want to use cattrs to structure dicts and other stuff to attrs. However, I am unhappy with the default behaviour that if attrs expects an int, float, str, bool or something like that, it casts the object to that type using int(), float(), str(), or bool() respectively. I want cattrs to structure containers such as sequences, dicts, etc. but I don't want it to typecast base types - I want it to error out instead if the types don't match. How can I do this?

Tinche commented 9 months ago

Hello!

You can override the default hooks for those types to be anything you like. Cattrs is based on function composition to this is how problems are usually solved.

A simple solution would be like this:

from cattrs import Converter

c = Converter()

def structure_int(val, _) -> int:
    if not isinstance(val, int):
        raise ValueError("Not an int")
    return val

c.register_structure_hook(int, structure_int)

c.structure("1", int)  # Error

You can get fancier by making use of the fact structure hooks receive their target type as their second argument, like this:

c = Converter()

def validate_type(val, type):
    if not isinstance(val, type):
        raise ValueError(f"Not a {type}")
    return val

c.register_structure_hook(int, validate_type)
c.register_structure_hook(float, validate_type)
c.register_structure_hook(str, validate_type)
c.register_structure_hook(bool, validate_type)

c.structure("1", bool)

These rules propagate to collections, attrs classes etc.

Tinche commented 9 months ago

I'm planning a strategy to make this easier, probably in the next version.