doorstop-dev / doorstop

Requirements management using version control.
https://doorstop.readthedocs.io
Other
474 stars 132 forks source link

Provide the Dumper class explicitly when calling frontmatter.dumps #584

Closed mjeronimo closed 1 year ago

mjeronimo commented 1 year ago

When trying to use extended attributes with Doorstop requirements, I've encountered an issue with representers.

Here is a description of the issue with a test case:

import frontmatter
import yaml

#
# When reading the Markdown, Doorstop wraps the string with its own _Literal class. 
# This class has a static representer method.
#
class _Literal(str):
    """Custom type for text which should be dumped in the literal style."""

    @staticmethod
    def representer(dumper, data):
        """Return a custom dumper that formats str in the literal style."""
        return dumper.represent_scalar(
            "tag:yaml.org,2002:str", data, style="|" if data else ""
        )

#
# Doorstop adds this representer, using yaml.add_representer, which is in the
# yaml module's __init__.py.  This method takes and optional paramer, Dumper, 
# which, in this case, defaults to the yaml.dumper.Dumper class.
#
yaml.add_representer(_Literal, _Literal.representer)

#
# Here is an example requirement, with additional fields, and simulating Doorstop's conversion of a
# string field to _Literal (which is what Doorstop does with the ptLTL field).
#
content = 'FSM shall always satisfy if (standby & state = ap_transition_state) then STATE = ap_standby_state'
data = {
  'level': 1,
  'active': True,
  'normative': True,
  'derived': False,
  'reviewed': 'none',
  'ref': '',
  'links': [],
  'project': 'Demo-FSM',
  'rationale': 'The autopilot shall change states from TRANSITION to STANDBY when the pilot is in control (standby).',
  'comments': None,
  'ptLTL': _Literal('H standby & state = ap_transition_state -> STATE = ap_standby_state'),
}

#
# When dumping, the dumps method from frontmatter class used the default Dumper, which in thise case is 
# yaml.cyaml.CSafeDumper. This Dumper doesn't have the additional representer for _Literal (it was only
# added to yaml.dumper.Dumper), which causes the failure
#
text = frontmatter.dumps(frontmatter.Post(content, **data))

# Instead, if the dumps call explicitly uses the same dumper class, yaml.dumper.Dumper, as was originally
# used when adding the representer, the code works:
#
#text = frontmatter.dumps(frontmatter.Post(content, **data), Dumper=yaml.dumper.Dumper)

print(text)

Signed-off-by: Michael Jeronimo michael.jeronimo@openrobotics.org

jacebrowning commented 1 year ago

This change is available here: https://pypi.org/project/doorstop/3.0b7/