Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support type variations in ConcreteType and converter fallbacks #12

Open
NiklasRosenstein opened this issue Aug 13, 2021 · 0 comments
Open

Comments

@NiklasRosenstein
Copy link
Owner

Use case: A type that supports multiples representations and modes of de-/serialization.

Example: Imagine a dataclass Author that can be deserialized from a string or from a mapping.

@dataclass
class Author:
  AUTHOR_EMAIL_REGEX = re.compile(r'([^<]+)<([^>]+)>')
  name: str
  email: str

  @classmethod
  def parse(cls, string: str) -> 'Author':
    match = Author.AUTHOR_EMAIL_REGEX.match(string)
    if not match:
      raise ValueError('not a valid author string: {!r}'.format(string))
    name = match.group(1).strip()
    email = match.group(2).strip()
    return cls(name, email)

  def __str__(self):
    return '{} <{}>'.format(self.name, self.email)

We can register a converter from/to string like this:

class StringConverter(Converter):

  def __init__(self, loader: t.Optional[t.Callable[[str], t.Any]] = None) -> None:
    self._loader = loader

  def convert(self, ctx: Context) -> object:
    assert isinstance(ctx.type, ConcreteType)
    if ctx.direction == Direction.serialize:
      assert isinstance(ctx.value, ctx.type.type)
      return str(ctx.value)
    else:
      if not isinstance(ctx.value, str):
        raise ctx.type_error(expected=str)
      if self._loader:
        return self._loader(ctx.value)
      else:
        return ctx.type.type.from_string(ctx.value)

mapper.add_converter_for_type(Author, StringConverter(Author.parse)) 

Drawbacks:

Workaround: In the deserialization of the Author type, we could check if we're dealing with a mapping and delegate to the ObjectTypeConverter instead. (The serialization case is a bit more tricky if you want to maintain the state mode which the value was deserialized and we can think about that in a different issue).

      if isinstance(ctx.value, t.Mapping):
        ctx = ctx.push(ObjectType(dataclass_to_schema(ctx.type.type, ctx.type_hint_adapter), []), ctx.value, None)
        return self._fallback.convert(ctx)

Potential solution:

  • We can require type-hint adapters to store the original that a type was adapter from, allowing the ObjectMapper to look for an alternative converter for the original type if a ConversionError caused by an incompatible source value type (only during deserialization)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant