Skip to content

[WIP] Add content negotiation to support text/markdown#2012

Open
tisto wants to merge 10 commits intomainfrom
plip-1997-poc
Open

[WIP] Add content negotiation to support text/markdown#2012
tisto wants to merge 10 commits intomainfrom
plip-1997-poc

Conversation

@tisto
Copy link
Copy Markdown
Member

@tisto tisto commented Apr 6, 2026

  • I signed and returned the Plone Contributor Agreement, and received and accepted an invitation to join a team in the Plone GitHub organization.
  • I verified there aren't any other open pull requests for the same change.
  • I followed the guidelines in Contributing to Plone.
  • I successfully ran code quality checks on my changes locally.
  • I successfully ran tests on my changes locally.
  • If needed, I added new tests for my changes.
  • If needed, I added documentation for my changes.
  • I included a change log entry in my commits.

If your pull request closes an open issue, include the exact text below, immediately followed by the issue number. When your pull request gets merged, then that issue will close automatically.

Closes #


📚 Documentation preview 📚: https://plonerestapi--2012.org.readthedocs.build/

@mister-roboto
Copy link
Copy Markdown

@tisto thanks for creating this Pull Request and helping to improve Plone!

TL;DR: Finish pushing changes, pass all other checks, then paste a comment:

@jenkins-plone-org please run jobs

To ensure that these changes do not break other parts of Plone, the Plone test suite matrix needs to pass, but it takes 30-60 min. Other CI checks are usually much faster and the Plone Jenkins resources are limited, so when done pushing changes and all other checks pass either start all Jenkins PR jobs yourself, or simply add the comment above in this PR to start all the jobs automatically.

Happy hacking!

@read-the-docs-community
Copy link
Copy Markdown

read-the-docs-community bot commented Apr 6, 2026

Documentation build overview

📚 plone.restapi | 🛠️ Build #32165276 | 📁 Comparing cf6e5ab against latest (92c7631)

  🔍 Preview build  

Show files changed (1 files in total): 📝 1 modified | ➕ 0 added | ➖ 0 deleted
File Status
endpoints/types.html 📝 modified

tisto added 2 commits April 6, 2026 09:28
…st of types, it needs to be application/json, when we return individual types, we return application/json+schema.
Copy link
Copy Markdown
Member

@davisagli davisagli left a comment

Choose a reason for hiding this comment

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

@tisto Thanks for starting a proof of concept. I guess you want something to try out and not a detailed review at this stage. But here are a few thoughts I had from my first look at it.

@implementer(IRenderer)
@adapter(Interface)
class MarkdownRenderer:
"""Render data as GitHub Flavored Markdown with YAML frontmatter."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Unlike the JSONRenderer, this is really only designed to render a particular type of data (a content item in the format produced by an ISerializeToJson adapter). That makes me wonder if you're trying to generalize too much by using this in the base Service class instead of only the ContentGet service.

Do we need to return a markdown representation from other services as well?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a very good point. I am guessing that we only need the content endpoints to return markdown.

body_parts.append(str(value))
else:
# Everything else goes to frontmatter
frontmatter[key] = value
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This would be more extensible if it was handled by field adapters rather than a hardcoded set of if/else statements based on hardcoded lists of fields. Of course, it's fine as a starting point for a proof of concept.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did not look at the Markdown generation code at all so far. I noticed that it does not look pretty though. Right now I am interested in looking at the architectural level. The details of the Markdown renderer is something that we can cover at a later point IMO.

return "\n".join(parts)

def _render_yaml(self, data, indent=0):
"""Convert a Python dict to YAML format."""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

YAML serialization is hard to get right. For example, did you know that if the string "no" is serialized as no (unquoted), then it will be deserialized as boolean False?

So I would be inclined to use a battle-tested yaml library rather than roll our own.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point. I am not fully sold on the YAML idea and I do not have much experience with using it on a programmatic level. Using a library is definitely something we should do if we decide on YAML.

if url:
return f"![{alt}]({url})"

# Return None for complex blocks
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could be made extensible using adapters


# Fallback to JSON (default)
renderer = queryAdapter(self.request, IRenderer, name="application/json")
if renderer is not None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This should never be None, since you registered one in this package. So I think the fallback below is unnecessary.

# Wildcard - use default JSON
continue

renderer = queryAdapter(self.request, IRenderer, name=mime_type)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Maybe this should also adapt the service instance, so that it's possible to register a renderer for a specific service. (The markdown renderer here is registered for Interface, but is really only designed to work with the ContentGet service.)


<plone:service
method="GET"
accept="application/json,text/markdown,*/*"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Does this actually do anything?

@tisto
Copy link
Copy Markdown
Member Author

tisto commented Apr 8, 2026

@davisagli my goal with this PR is to have a PoC that allows me to raise questions regarding architectural decisions that we would have to make to add markdown support to plone.restapi. Therefore, comments are welcome, but please do treat this as what it is: a vibe-coded PoC that we most likely will throw away and start from scratch, once we have a clear picture of where we want to go. :)

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.

3 participants