Skip to content

Support customizing tagged union tag values#660

Open
mickvangelderen wants to merge 9 commits intoyukinarit:mainfrom
mickvangelderen:mick/customize-union-tags
Open

Support customizing tagged union tag values#660
mickvangelderen wants to merge 9 commits intoyukinarit:mainfrom
mickvangelderen:mick/customize-union-tags

Conversation

@mickvangelderen
Copy link
Copy Markdown
Contributor

No description provided.

@mickvangelderen mickvangelderen force-pushed the mick/customize-union-tags branch from 77c8c86 to 123a7ab Compare August 22, 2025 17:49
Comment thread serde/core.py
tag: Optional[str] = None
content: Optional[str] = None
kind: Kind = Kind.External
tags: Optional[frozendict[type[Any], str]] = None
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Using frozendict so the value can be hashed.

Comment thread serde/de.py
Comment on lines +1132 to +1133
ensure("{{tagging.tag_for(t)}}" in data , "'{{tagging.tag_for(t)}}' key is not present")
fake_dict = {"fake_key": data["{{tagging.tag_for(t)}}"]}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wonder if we could inline the tags as literals, rather than looking them up during deserialization.

Comment thread serde/core.py
Comment on lines +877 to 895

@overload
def ExternalTagging_(cls: T, *, tags: dict[type[Any], str]) -> _WithTagging[T]: ...


def ExternalTagging_(cls: Optional[T] = None, *, tags: dict[type[Any], str]) -> Union[Tagging, _WithTagging[T]]:
tagging = Tagging(kind=Tagging.Kind.External, tags=tags)
if cls:
return tagging(cls)
else:
return tagging


# TODO: This is an instance rather than a function for backwards-compatibility
# reasons. In the next major version increase this should be replaced with a
# function.
ExternalTagging = Tagging()

Untagged = Tagging(kind=Tagging.Kind.Untagged)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is unfortunate, not sure what to call the function.

@codecov
Copy link
Copy Markdown

codecov Bot commented Aug 22, 2025

Codecov Report

❌ Patch coverage is 90.90909% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.81%. Comparing base (e191403) to head (722edac).
⚠️ Report is 88 commits behind head on main.

Files with missing lines Patch % Lines
serde/core.py 90.90% 0 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #660      +/-   ##
==========================================
- Coverage   90.91%   90.81%   -0.11%     
==========================================
  Files          13       13              
  Lines        2004     2057      +53     
  Branches      367      373       +6     
==========================================
+ Hits         1822     1868      +46     
- Misses        123      125       +2     
- Partials       59       64       +5     

☔ 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 thread serde/core.py
Comment on lines +826 to +848
tag = casefy.pascalcase(self.tag) if self.tag is not None else None
content = casefy.pascalcase(self.content) if self.content is not None else None
if self.tags is not None:
pairs = sorted(self.tags.items(), key=lambda pair: typename(pair[0]))
parts = (f"{typename(typ)}_{tag}" for typ, tag in pairs)
tags = re.sub(r"[^A-Za-z0-9]", "_", "_".join(parts))
else:
tags = ""

if self.is_internal():
tag = casefy.pascalcase(self.tag) # type: ignore
if not tag:
if tag is None:
raise SerdeError('"tag" must be specified in InternalTagging')
return f"Internal{tag}"
base_name = f"Internal{tag}{tags}"
elif self.is_adjacent():
tag = casefy.pascalcase(self.tag) # type: ignore
content = casefy.pascalcase(self.content) # type: ignore
if not tag:
if tag is None:
raise SerdeError('"tag" must be specified in AdjacentTagging')
if not content:
if content is None:
raise SerdeError('"content" must be specified in AdjacentTagging')
return f"Adjacent{tag}{content}"
base_name = f"Adjacent{tag}{content}{tags}"
else:
return self.kind.name
base_name = f"{self.kind.name}{tags}"

return base_name
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wonder if it would be possible and better to use the Tagging instance as the cache key. It is not obvious how to turn it into a string. Even before this PR, the tag and content are converted to pascal case which I think shouldn't work, unless deserialization is case-insensitive with respect to the those keys.

Sign up for free to 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.

1 participant