Skip to content

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

Draft
tisto wants to merge 36 commits into
mainfrom
plip-1997-poc
Draft

tisto wants to merge 36 commits into
mainfrom
plip-1997-poc

Conversation

@tisto

@tisto tisto commented Apr 6, 2026 •

Copy link
Copy Markdown
Member
  • 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

read-the-docs-community Bot commented Apr 6, 2026 •

Copy link
Copy Markdown

Documentation build overview

📚 plone.restapi | 🛠️ Build #35242323 | 📁 Comparing 2bd5f8c against latest (5555b28)

  🔍 Preview build  

7 files changed · ± 7 modified

± 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.

@davisagli davisagli left a comment

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.

@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.

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.

@davisagli I don't quite get how thats supposed to work, by adapting IFieldSerializer?

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.

@jnptk Not directly. IFieldSerializer adapters take care of converting the value of different types of fields into a representation that is ready to be serialized to JSON. But here we want markdown, and for each field we might want to add some things to the frontmatter and some to the body.

I guess we want a new interface IMarkdownFieldSerializer that is something like this:

class IMarkdownFieldSerializer:
    """Adapt a field to return markdown"""

    def get_frontmatter(value) -> dict:
        """Return a dict to be added to frontmatter"""

    def get_body_parts(value) -> list:
        """Return a list of markdown strings to be added to the body"""

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 is brainstorming. We could certainly ship a first version of this that is not flexible using adapters.)

Comment thread src/plone/restapi/renderer/markdown.py Outdated
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.

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.

I replaced _render_yaml with https://pypi.org/project/PyYAML

Comment thread src/plone/restapi/renderer/markdown.py Outdated
Comment thread src/plone/restapi/services/__init__.py Outdated
Comment thread src/plone/restapi/services/__init__.py Outdated

<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?

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.

yes, without this ContentGet service returns text/html, which makes me wonder how it worked before when there was no accept on the directive, am I missing something?

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.

I guess it is because of what plone.rest does based on this attribute for the directive.

@tisto

tisto commented Apr 8, 2026

Copy link
Copy Markdown
Member Author

@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. :)

@jnptk

jnptk commented Oct 15, 2026

Copy link
Copy Markdown
Member

@jenkins-plone-org please run jobs

@jensens
jensens marked this pull request as draft October 19, 2026 20:54
@jensens

jensens commented Oct 19, 2026

Copy link
Copy Markdown
Member

Since this is WIP i converted it to a draft to mark this more prominently.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants