Skip to content

first attempt at an adjacently tagged union for consideration - #94

Draft
altendky wants to merge 60 commits into
python-desert:mainfrom
altendky:36-altendky-first_attempt
Draft

first attempt at an adjacently tagged union for consideration#94
altendky wants to merge 60 commits into
python-desert:mainfrom
altendky:36-altendky-first_attempt

Conversation

@altendky

@altendkyaltendky commented Mar 12, 2020

Copy link
Copy Markdown
Member

#36

Draft for:

@altendkyaltendky changed the title first attempt at an adjacently tagged union for consideration[WIP] first attempt at an adjacently tagged union for considerationMar 12, 2020
@codecov

codecovBot commented Mar 12, 2020

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (0812d5f) to head (ea773b7).
⚠️ Report is 29 commits behind head on main.

Additional details and impacted files
@@ Coverage Diff @@## main #94 +/- ##
==========================================
Coverage 100.00% 100.00% ==========================================
Files 6 9 +3 Lines 468 820 +352 Branches 69 106 +37 ==========================================
+ Hits 468 820 +352 

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadtests/test_fields.py Outdated
this gives much more interesting test results where you can see
the deserialization is fine due to the unique tags but the serialization
is not.
Comment threadsrc/desert/_fields.py Outdated

@attr.s(auto_attribs=True)
class TypeDictFieldRegistry:
the_dict: typing.Dict[typing.Union[type, str], marshmallow.fields.Field] = attr.ib(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a big the_foo user, perhaps .mapping or something.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, yeah, that was a garbage name so as to not think about it and worry about other things. Could also just be .dict

@altendky

altendky commented May 31, 2020

Copy link
Copy Markdown
MemberAuthor

Oh hey... look. We can't add the pytypes PR as a dependency because you can't use a non-pypi dependency in install_requires and released pytypes only supports up to 3.6. :]

@desert-botdesert-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need a clearer presentation of the relational properties of the Registry idea. There are several entity categories: tag, field, class, hint. For each category C, I want a list of which of the others are required to retrieve a unique instance of C. For example, "a w is uniquely identifiable from a (x,z) tuple or a (y,z) tuple" where w,x,y,z are standing in for some of {tag,field,class,hint}.

Comment threadtests/test_fields.py
registry.register(
hint=Cat,
tag="cat",
field=marshmallow.fields.Nested(desert.schema(Cat, meta={"ordered": True})),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird for a standalone class to be registered as a Nested.

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought this was how you made a field from a class. But yes, a person having to write this is at least questionable UX. Added to the Draft for: list in the OP.

Comment threadtests/test_fields.py Outdated
@altendky

Copy link
Copy Markdown
MemberAuthor
  • I'll start by writing it here, but presumably some form of this belongs in the docs.

I need a clearer presentation of the relational properties of the Registry idea. There are several entity categories: tag, field, class, hint. For each category C, I want a list of which of the others are required to retrieve a unique instance of C. For example, "a w is uniquely identifiable from a (x,z) tuple or a (y,z) tuple" where w,x,y,z are standing in for some of {tag,field,class,hint}.

P.S. A direct answer is at the end. This was a good refresher for myself and I leave it here in case it is useful. Copied here.

So I guess the summary of my self-reacquaintance with this code is that the present implementation in TypeAndHintFieldRegistry selects a field/tag pair based on a hint for serialization and for deserialization it takes a tag and selects a field.

It may be that this mechanism should have a higher level of formality, but I will start by trying to explain what is already here. I think the registry protocol describes the needed functionality and then we can move on to the provided implementation.

https://github.com/altendky/desert/blob/ea773b7bc7df6591ff7c0e538e2f96a11637657b/src/desert/_fields.py#L32-L61

classFieldRegistryProtocol(typing_extensions.Protocol):
"""This protocol encourages registries to provide a common interface. The actual implementation of the mapping from objects to be serialized to their Marshmallow fields, and likewise from the serialized data, can take any form. """defregister(
self,
hint: t.Any,
tag: str,
field: marshmallow.fields.Field,
) ->None:
"""Inform the registry of the relationship between the passed hint, tag, and field. """
...
@propertydeffrom_object(self) ->"FromObjectProtocol":
"""This is a funny way of writing that the registry's `.from_object()` method should satisfy :class:`FromObjectProtocol`. """
...
@propertydeffrom_tag(self) ->"FromTagProtocol":
"""This is a funny way of writing that the registry's `.from_tag()` method should satisfy :class:`FromTagProtocol`. """
...

(I wonder if I should (could?) make a function to create those protocol-conforming methods...)

https://github.com/altendky/desert/blob/ea773b7bc7df6591ff7c0e538e2f96a11637657b/src/desert/_fields.py#L186-L193

classFromObjectProtocol(typing_extensions.Protocol):
def__call__(self, value: object) ->HintTagField:
...
classFromTagProtocol(typing_extensions.Protocol):
def__call__(self, tag: str) ->HintTagField:
...

Once configured, the registry needs to be able to process either an object to be serialized (.from_object() / FromObjectProtocol) or a tag (.from_tag()/ FromTagProtocol). The result must be a HintTagField which provides the calling field with the .field and .tag attributes for serialization and just the .field attribute for deserialization.

  • I think that HintTagField.hint may be used only in the registry implementation and not the interface so perhaps it should be adjusted to represent that. Or perhaps the two directions shouldn't even mandate the same object, though it doesn't seem immediately obviously onerous to provide a tag when deserializing.

So... the interface does not mandate a whole lot. You could satisfy it with random return values. That's certainly a bit extreme, but I had avoided thus far thinking through what weird corners people might find for oddball relationships between hints, tags, and fields.

So, now for the provided implementation, TypeAndHintFieldRegistry. It does still have some heuristics so I'm not certain it breaks down to a simple formal mathematical mapping etc. .from_object() (for serialization) iterates over its HintTagFields and gives points to each based on:

  • typeguard.check_type() against the hint
  • an isinstance() check against the hint
  • comparison of the object's type against the hint's typing_inspect.get_origin(), just for disambiguation

So I guess for now it is using just the hint to resolve a given object to a field and tag.

.from_tag() is straightforward, which is of course the entire point of a tagging to begin with... Only one HintTagField is allowed with a given tag and so any tag directly maps to a single HintTagField, of which only the field is used in deserialization presently.

So I guess the summary of my self-reacquaintance with this code is that the present implementation in TypeAndHintFieldRegistry selects a field/tag pair based on a hint for serialization and for deserialization it takes a tag and selects a field.

@desert-bot

Copy link
Copy Markdown
Collaborator

Thanks, I'm starting to get the picture.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@altendky@desert-bot@python-desert