Skip to content
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

Stubs for public interface #58

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

Conversation

xoudini
Copy link

@xoudini xoudini commented Jul 10, 2024

This is a minimal PR, which (mostly) resolves #50, as discussed in #57.

For overriding components in downstream projects, the public interface of all components should be typed accordingly, but I'll leave that for the next PR (unless it's seen as pertinent to do here while we're at it).

For instance, calls like this:

def render(self):
content = super().render()
return f'<div>***</div>{content}<div>***</div>'

...will cause mypy to error with:

error: Call to untyped function "render" in typed context  [no-untyped-call]

The same goes for e.g. default_attrs and so on, so type annotations should be added to these as soon as possible.

@xoudini
Copy link
Author

xoudini commented Jul 10, 2024

See mypy docs on stubs for more details.

The stubs can be deleted later, when the package is typed internally.

@xoudini
Copy link
Author

xoudini commented Jul 11, 2024

Also added type hints for the dictionary interface of the output (since dotmap.DotMap is also a dictionary). These can be removed if attribute access on the result should be enforced (or if treating the result as a dictionary should be discouraged).

Copy link
Owner

@FelixSchwarz FelixSchwarz left a comment

Choose a reason for hiding this comment

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

Sorry for the long delay. I just added a few minor comments but other than that it looks good to merge.

mjml/__init__.pyi Outdated Show resolved Hide resolved
def mjml_to_html(
xml_fp_or_json: "FpOrJson",
skeleton: t.Optional[str] = None,
template_dir: t.Optional["StrOrPath"] = None,
Copy link
Owner

Choose a reason for hiding this comment

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

This is one of the places where types are really helpful as I looked through the code and I am not sure if a str instance is really acceptable. It seems to me at first glance that we are doing template_dir / foo unconditionally so this should be os.PathLike.

Maybe you can check my finding? (I am probably overlooking something)

Copy link
Author

@xoudini xoudini Aug 14, 2024

Choose a reason for hiding this comment

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

It seems to me at first glance that we are doing template_dir / foo unconditionally so this should be os.PathLike.

This should be fine, as long as foo is a pathlib.PurePath (or derivative thereof), and it looks to me like this is in fact the case. I.e. pathlib.PurePath implements this operator for both left-hand and right-hand joins.

I used this annotation initially because there's already some string usage (as far as I can tell), for instance here:

result = mjml_to_html(mjml_fp, template_dir=template_dir)

@te.overload
def get(self, key: t.Literal["errors"], default: t.Sequence[str], /) -> t.Sequence[str]: ...

FpOrJson = t.Union[t.Dict[str, t.Any], str, bytes, SupportsRead[str], SupportsRead[bytes]]
Copy link
Owner

Choose a reason for hiding this comment

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

Probably bike-shedding but does mjml_to_html() works if xml_fp_or_json contains a bytes value?

Copy link
Author

@xoudini xoudini Aug 14, 2024

Choose a reason for hiding this comment

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

The BeatifulSoup constructor has the following annotation (in the stubs):

def __init__(
    self,
    markup: str | bytes | SupportsRead[str] | SupportsRead[bytes] = "",
    # ...
) -> None: ...

Purely based on that it should work, since bytes is left untouched by mjml_to_html, but I also quickly tried it, with a minimal example, and it seems to work just fine:

from mjml import mjml_to_html

mjml_to_html(b"<mjml><mj-body><mj-raw>foo</mj-raw></mj-body></mjml>")

# DotMap(html='<!doctype html>\n<html xmlns="http://www.w3.org/1999/xhtml" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:o="urn:schemas-microsoft-com:office:office">\n  <head>\n    <title>\n      \n    </title>\n    <!--[if !mso]><!-->\n    <meta http-equiv="X-UA-Compatible" content="IE=edge">\n    <!--<![endif]-->\n    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n    <meta name="viewport" content="width=device-width, initial-scale=1">\n    <style type="text/css">\n      #outlook a { padding:0; }\n      body { margin:0;padding:0;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%; }\n      table, td { border-collapse:collapse;mso-table-lspace:0pt;mso-table-rspace:0pt; }\n      img { border:0;height:auto;line-height:100%; outline:none;text-decoration:none;-ms-interpolation-mode:bicubic; }\n      p { display:block;margin:13px 0; }\n    </style>\n    <!--[if mso]>\n    <noscript>\n    <xml>\n    <o:OfficeDocumentSettings>\n      <o:AllowPNG/>\n      <o:PixelsPerInch>96</o:PixelsPerInch>\n    </o:OfficeDocumentSettings>\n    </xml>\n    </noscript>\n    <![endif]-->\n    <!--[if lte mso 11]>\n    <style type="text/css">\n      .mj-outlook-group-fix { width:100% !important; }\n    </style>\n    <![endif]-->\n    \n    \n    <style type="text/css">\n\n    \n    \n    </style>\n    <style type="text/css"></style>\n    \n  </head>\n  <body style="word-spacing:normal;">\n    \n    <div style="">foo</div>\n  </body>\n</html>', errors=[])

@xoudini
Copy link
Author

xoudini commented Aug 14, 2024

Sorry for the long delay. I just added a few minor comments but other than that it looks good to merge.

No worries at all!

Speaking of bike-shedding, there's currently an annotation for custom_components expecting an optional list:

def mjml_to_html(xml_fp_or_json, skeleton=None, template_dir=None,
custom_components: Optional[List]=None):

Since this list isn't actually mutated, it would be sufficient to annotate it as a typing.Sequence (or collections.abc.Sequence once Python 3.9 is used as the baseline). Should I update these while I'm at it, or leave it for later?

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.

Type hints for public interface?
2 participants