Python attr.s multiple type validation
A class that generates a UUID by default if none is provided, and validates/creates a UUID object if a str
is provided.
import attr
from attrs import validators
from uuid import UUID, uuid1
def _validate(instance, attribute, value) -> None:
try:
if isinstance(value, str):
instance.uuid = UUID(value)
return
except Exception as e:
raise BadUUID() from e
@attr.s(slots=True)
class Private:
uuid = attr.ib(type=[str, UUID], validator=[validators.optional([validators.instance_of(UUID), validators.instance_of(str)]), _validate], default=uuid1())
It should with and without providing a value:
print(Private())
print(Private('d283a713-9f4b-1c15-ab8d-8d95d7ce8999'))
In the case there is none provided it should generate a new UUID using the default setting.
If value is provided it should validated it and create a UUID object.
I get an error because it will only validate one instance type, either str
or UUID
.
If I set instance_of(UUID)
it will only work without providing a value.
And if I set it to instance_of(str)
it will only work with a str
being provided.
Am I doing something wrong, is there a better way to accomplish what I'm looking for?
It seems this is what you're after:
import attr
from uuid import UUID, uuid1
def _convert(value) -> UUID:
return value if isinstance(value, UUID) else UUID(value)
@attr.s(slots=True)
class Private:
uuid = attr.ib(default=uuid1(), converter=_convert)
p1 = Private()
print(p1.uuid)
p2 = Private('c53358b3-798e-11ec-a49b-cf6d4243e811')
print(p2.uuid)
Example result:
d5e2d087-798e-11ec-9d59-cf6d4243e811
c53358b3-798e-11ec-a49b-cf6d4243e811
If you prefer that only strings are converted and your own exception is raised if anything else was passed (although I'd probably just leave it up to UUID()
itself), this works:
import attr
from uuid import UUID, uuid1
class BadUUID(Exception):
...
def _convert(value) -> UUID:
if isinstance(value, str):
return UUID(value)
elif isinstance(value, UUID):
return value
else:
raise BadUUID(f'{value} is neither a string nor a UUID')
@attr.s(slots=True)
class Private:
uuid = attr.ib(default=uuid1(), converter=_convert)
p = Private(42)
Or if your intent was to catch exceptions from UUID and add something:
import attr
from uuid import UUID, uuid1
class BadUUID(Exception):
...
def _convert(value) -> UUID:
try:
return value if isinstance(value, UUID) else UUID(value)
except Exception as e:
raise BadUUID (f'{value} is not a good UUID') from e
@attr.s(slots=True)
class Private:
uuid = attr.ib(default=uuid1(), converter=_convert)
p = Private(42)