Skip to content

Registering custom YAML tags #260

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

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

bmispelon
Copy link

Description

I recently learned about registering/using custom YAML tags and they pair well with Django Pattern Library. Once thing though is that custom tags are currently registered globally and could therefore theoretically have unwanted side effect on the application if it uses yaml too.

To solve this, I've split this PR into two commits:

  • First, a small one that simply creates a new yaml.FullLoader subclass and uses that when loading a context. This way anyone who wants to register a custom tag can do so on the pattern library's custom loader and not on the global one.
  • The second commit is more substantial and introduces a decorator to make it easier to register custom tags.

There's also a bonus commit that fixes a typo in the code examples that I found by accident while writing my own documentation.

Let me know if I should also create an issue to track this, I'd be happy to!

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added an appropriate CHANGELOG entry

@bmispelon bmispelon force-pushed the custom-yaml-loader branch 2 times, most recently from 45cadff to f502b7b Compare June 6, 2025 11:46
@bmispelon bmispelon force-pushed the custom-yaml-loader branch from f502b7b to 24db917 Compare June 6, 2025 11:49
@bmispelon bmispelon marked this pull request as ready for review June 6, 2025 11:50
@thibaudcolas thibaudcolas added enhancement New feature or request django Related to Django templates capabilities labels Jun 6, 2025
"""
# PyYAML doesn't provide an inverse operation for add_constructor(), so
# we need to do it manually.
del PatternLibraryLoader.yaml_constructors[f"!{name}"]
Copy link
Member

Choose a reason for hiding this comment

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

I'm not familiar with the pyyaml internals, but won't this mutate the yaml_constructors list of the parent class, FullLoader?

Copy link
Author

Choose a reason for hiding this comment

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

Great question! I believe it won't, because of the copy() there: https://github.com/yaml/pyyaml/blob/69c141adcf805c5ebdc9ba519927642ee5c7f639/lib/yaml/constructor.py#L162

But I should probably add a test for that behavior to be certain.

Copy link
Member

Choose a reason for hiding this comment

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

Ohhh that makes sense. Thanks for checking.

Copy link
Member

@thibaudcolas thibaudcolas left a comment

Choose a reason for hiding this comment

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

Nice! No need to create an issue, though if you have the chance it’s always appreciated to check if there are related issues where this is worth mentioning / that can be triaged with this change in mind (#155 ?)

  • I know nothing about YAML tags and PyYAML. Could you either find someone who does to review this, or provide guidance on which parts of the code need attention?
  • I think it would be nice to have an additional example in the docs for this that isn’t Wagtail-related.
  • Could you add examples of this in the pattern library demo, so it’s easier to manually test? Those can be based on your test cases, or something more advanced that better demonstrates the benefits.
  • As part of this, could you help us triage Can't use Python object YAML tags #155? Seems like that issue would still be relevant, possibly, though with your changes we might be ok to fully treat "no support for arbitrary Python object tags" as a feature and close that issue.

if "main_image" in context["page"]:
context["main_image"] = Image.objects.all().order("?").first()
context["main_image"] = Image.objects.all().order_by("?").first()
Copy link
Member

Choose a reason for hiding this comment

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

Nice!


## Extending the YAML syntax

You can also take advantage of YAML's local tags in order to insert full-fledged Python objects into your mocked contexts.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
You can also take advantage of YAML's local tags in order to insert full-fledged Python objects into your mocked contexts.
You can also take advantage of [YAML's local tags](https://yaml.org/spec/1.2.2/#tags) in order to insert full-fledged Python objects into your mocked contexts.

I think an external explainer link would be good here? Have added one but it’s a bit dry. If you have a better one please add it?

from wagtail.images import get_image_model

@register_yaml_tag
def testimage():
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
def testimage():
def test_image():

testimage and concatenatedwordslikethis are problematic for accessibility – not very readable, don’t work well with text-to-speech (screen readers). So let’s add an underscore.

Also I think this will be a lot of people’s first contact with YAML tags, so well worth it to polish this example more if there are ways? Though "test image" is a reasonable start.

image: !testimage
- title: Second item
image: !testimage
```
Copy link
Member

Choose a reason for hiding this comment

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

GitHub’s syntax highlighting seems to fail from here onwards. Not a dealbreaker but just wanted to check if we should mention compatibility of local tags in YAML with different IDEs / linters? For example some projects will use Prettier’s auto-formatting for YAML, not sure if local tags in there are a compatibility problem that warrants mentioning.

Comment on lines +247 to +248
def get_random_image():
...
Copy link
Member

Choose a reason for hiding this comment

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

Is this not missing the name=?


### Passing arguments to custom tags

It's possible to create custom tags that take arguments.
Copy link
Member

Choose a reason for hiding this comment

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

Say more about why this is useful? Something like:

Suggested change
It's possible to create custom tags that take arguments.
It's possible to create custom tags that take arguments, to get more control over how the mock data is generated.

Even better to have a more complete example

return constructor


def register_yaml_tag(fn=None, name=None):
Copy link
Member

Choose a reason for hiding this comment

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

The package’s public API uses type hints, so this will need adding here. Same for unregister_yaml_tag. It’s fine for the implementation and tests to not be typed.

@mgax
Copy link
Member

mgax commented Jun 6, 2025

  • Could you either find someone who does to review this, or provide guidance on which parts of the code need attention?

I could review it. It's easier since I was involved with an internal task, that we decided to push upstream, so I have the context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
django Related to Django templates capabilities enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants