From c192292368983d2431919d9c3f92c1b7476d2855 Mon Sep 17 00:00:00 2001 From: Matthias Bussonnier Date: Wed, 22 Nov 2023 23:10:28 +0100 Subject: [PATCH] all things signatures and notes --- frontend/src/index.js | 95 +++++++++++------- papyri/crosslink.py | 3 +- papyri/miniserde.py | 189 +++++++++++++++++++---------------- papyri/myst_ast.py | 14 ++- papyri/myst_serialiser.py | 108 ++++++++++---------- papyri/render.py | 18 +++- papyri/signature.py | 1 + papyri/take2.py | 1 + papyri/templates/html.tpl.j2 | 1 + 9 files changed, 255 insertions(+), 175 deletions(-) diff --git a/frontend/src/index.js b/frontend/src/index.js index 5a0ae45f..67bfc7e8 100644 --- a/frontend/src/index.js +++ b/frontend/src/index.js @@ -7,16 +7,18 @@ import { MyST, DEFAULT_RENDERERS } from "myst-to-react"; import { fromMarkdown } from "mdast-util-from-markdown"; const Param = ({ node }) => { - return <> -
- {node.param}: {node.type_} -
-
- {node.desc.map((sub) => ( - - ))} -
- ; + return ( + <> +
+ {node.param}: {node.type_} +
+
+ {node.desc.map((sub) => ( + + ))} +
+ + ); }; const Parameters = ({ node }) => { return ( @@ -47,27 +49,51 @@ const DefList = ({ node }) => { ); }; +const ParameterNodeRenderer = ({ node }) => { + let acc = ""; + if (node.kind === "VAR_POSITIONAL") { + acc += "*"; + } + acc = acc + node.name; + if (node.default.type !== "Empty") { + acc += '='+node.default.data; + } + return acc; +}; + const SignatureRenderer = ({ node }) => { return ( - <> -
-
- {node.value} -
-
- -
- +
+ ONAME ( + <> + {node.parameters.map((parameter, index, array) => { + if (index + 1 == array.length) { + return ; + } else { + return ( + <> + + {", "} + + ); + } + })} + + ) +
); }; const Directive = ({ node }) => { - const dom = node.domain !== null ? ":"+node.domain : "" - const role = node.role !== null ? ":"+node.role+":" : "" + const dom = node.domain !== null ? ":" + node.domain : ""; + const role = node.role !== null ? ":" + node.role + ":" : ""; return ( <> - {dom}{role}`{node.value}` + + {dom} + {role}`{node.value}` + ); @@ -78,6 +104,7 @@ const LOC = { Directive: Directive, DefList: DefList, Parameters: Parameters, + ParameterNode: ParameterNodeRenderer, Param: Param, }; const RENDERERS = { ...DEFAULT_RENDERERS, ...LOC }; @@ -87,18 +114,18 @@ function MyComponent({ node }) { return ; } -const tree = fromMarkdown("Some *emphasis*, **strong**, and `code`."); -const mytree = { - type: "admonition", - children: [ - { type: "text", value: "myValue" }, - { - type: "signature", - value: "Foo", - children: [{ type: "text", value: "Child" }], - }, - ], -}; +//const tree = fromMarkdown("Some *emphasis*, **strong**, and `code`."); +//const mytree = { +// type: "admonition", +// children: [ +// { type: "text", value: "myValue" }, +// { +// type: "signature", +// value: "Foo", +// children: [{ type: "text", value: "Child" }], +// }, +// ], +//}; console.log("Loading X"); diff --git a/papyri/crosslink.py b/papyri/crosslink.py index fe11e5bf..ce2f4255 100644 --- a/papyri/crosslink.py +++ b/papyri/crosslink.py @@ -246,7 +246,8 @@ def _ingest_narrative(self, path, gstore: GraphStore) -> None: version=None, ) except Exception as e: - raise type(e)(f"at path: {document}") + e.add_note("at path: {document}") + raise ref = document.name module, version = path.name.split("_") diff --git a/papyri/miniserde.py b/papyri/miniserde.py index e241eac5..96100412 100644 --- a/papyri/miniserde.py +++ b/papyri/miniserde.py @@ -153,91 +153,110 @@ def serialize(instance, annotation): # type_ and annotation are _likely_ duplicate here as an annotation is likely a type, or a List, Union, ....) def deserialize(type_, annotation, data): - # assert type_ is annotation - # assert annotation != {} - # assert annotation is not dict - # assert annotation is not None, "None is handled by nullable types" - if annotation is str: - # assert isinstance(data, str) - return data - if annotation is int: - # assert isinstance(data, int) - return data - if annotation is bool: - # assert isinstance(data, bool) - return data - orig = getattr(annotation, "__origin__", None) - if data is None: - return None - if orig: - if orig is tuple: - # assert isinstance(data, list) - inner_annotation = annotation.__args__ - # assert len(inner_annotation) == 1, inner_annotation - return tuple( - deserialize(inner_annotation[0], inner_annotation[0], x) for x in data - ) - elif orig is list: - # assert isinstance(data, list) - inner_annotation = annotation.__args__ - # assert len(inner_annotation) == 1, inner_annotation - return [ - deserialize(inner_annotation[0], inner_annotation[0], x) for x in data - ] - elif orig is dict: - # assert isinstance(data, dict) - _, value_annotation = annotation.__args__ - return { - k: deserialize(value_annotation, value_annotation, x) - for k, x in data.items() - } - elif orig is Union: - inner_annotation = annotation.__args__ - if len(inner_annotation) == 2 and inner_annotation[1] == type(None): - # assert inner_annotation[0] is not None - if data is None: - return None + try: + # assert type_ is annotation + # assert annotation != {} + # assert annotation is not dict + # assert annotation is not None, "None is handled by nullable types" + if annotation is str: + # assert isinstance(data, str) + return data + if annotation is int: + # assert isinstance(data, int) + return data + if annotation is bool: + # assert isinstance(data, bool) + return data + orig = getattr(annotation, "__origin__", None) + if data is None: + return None + if orig: + if orig is tuple: + # assert isinstance(data, list) + inner_annotation = annotation.__args__ + # assert len(inner_annotation) == 1, inner_annotation + return tuple( + deserialize(inner_annotation[0], inner_annotation[0], x) + for x in data + ) + elif orig is list: + # assert isinstance(data, list) + inner_annotation = annotation.__args__ + # assert len(inner_annotation) == 1, inner_annotation + return [ + deserialize(inner_annotation[0], inner_annotation[0], x) + for x in data + ] + elif orig is dict: + # assert isinstance(data, dict) + _, value_annotation = annotation.__args__ + return { + k: deserialize(value_annotation, value_annotation, x) + for k, x in data.items() + } + elif orig is Union: + inner_annotation = annotation.__args__ + if len(inner_annotation) == 2 and inner_annotation[1] == type(None): + # assert inner_annotation[0] is not None + if data is None: + return None + else: + return deserialize( + inner_annotation[0], inner_annotation[0], data + ) + real_type = [t for t in inner_annotation if t.__name__ == data["type"]] + if len(real_type) == 0: + myst_type = f"M{data['type'][0].upper()}{data['type'][1:]}" + if data["type"] == "mystComment": + myst_type = "MComment" + elif data["type"] == "mystTarget": + myst_type = "MTarget" + elif data["type"] == "Transition": + myst_type = "thematicBreak" + real_type = [ + t + for t in inner_annotation + if (t.__name__ == myst_type) + or (getattr(t, "type", None) == myst_type) + ] + # assert len(real_type) == 1, real_type + try: + assert len(real_type) == 1, real_type + real_type = real_type[0] + except (IndexError, AssertionError) as e: + e.add_note( + f"""Index error as filters annotations are wrong myst_type={myst_type}, data[type]={data['type']}, + accepted:{inner_annotation} `t.type`?= {[getattr(t,"type", None) for t in inner_annotation]}""" + ) + raise + if data.get("data", _sentinel) is not _sentinel: + data_ = data["data"] else: - return deserialize(inner_annotation[0], inner_annotation[0], data) - real_type = [t for t in inner_annotation if t.__name__ == data["type"]] - if len(real_type) == 0: - myst_type = f"M{data['type'][0].upper()}{data['type'][1:]}" - if data["type"] == "mystComment": - myst_type = "MComment" - elif data["type"] == "mystTarget": - myst_type = "MTarget" - real_type = [t for t in inner_annotation if t.__name__ == myst_type] - # assert len(real_type) == 1, real_type - try: - real_type = real_type[0] - except IndexError: - breakpoint() - raise - if data.get("data", _sentinel) is not _sentinel: - data_ = data["data"] + data_ = {k: v for k, v in data.items() if k != "type"} + return deserialize(real_type, real_type, data_) else: - data_ = {k: v for k, v in data.items() if k != "type"} - return deserialize(real_type, real_type, data_) - else: - assert False - elif (type(annotation) is type) and annotation.__module__ not in ( - "builtins", - "typing", - ): - loc = {} - new_ann = get_type_hints(annotation).items() - # assert new_ann - for k, v in new_ann: - # assert k in data.keys(), f"{k} not int {data.keys()}" - # if data[k] != 0: - # assert data[k] != {}, f"{data}, {k}" - intermediate = deserialize(v, v, data[k]) - # assert intermediate != {}, f"{v}, {data}, {k}" - loc[k] = intermediate - if hasattr(annotation, "_deserialise"): - return annotation._deserialise(**loc) - else: - return annotation(**loc) + assert False + elif (type(annotation) is type) and annotation.__module__ not in ( + "builtins", + "typing", + ): + loc = {} + new_ann = get_type_hints(annotation).items() + # assert new_ann + for k, v in new_ann: + # assert k in data.keys(), f"{k} not int {data.keys()}" + # if data[k] != 0: + # assert data[k] != {}, f"{data}, {k}" + intermediate = deserialize(v, v, data[k]) + # assert intermediate != {}, f"{v}, {data}, {k}" + loc[k] = intermediate + if hasattr(annotation, "_deserialise"): + return annotation._deserialise(**loc) + else: + return annotation(**loc) - else: - assert False, f"{annotation!r}, {data}" + else: + assert False, f"{annotation!r}, {data}" + except Exception as e: + e.add_note(f"Deserializing {type_}, {annotation}") + raise diff --git a/papyri/myst_ast.py b/papyri/myst_ast.py index 768aca60..bc9489a0 100644 --- a/papyri/myst_ast.py +++ b/papyri/myst_ast.py @@ -16,6 +16,11 @@ class MText(Node): # position: Any # data: Any + def __init__(self, value): + assert isinstance(value, str) + self.value = value + super().__init__() + @register(4047) class MEmphasis(Node): @@ -208,7 +213,14 @@ class MHeading(Node): @register(4001) class MRoot(Node): type = "root" - children: List[Union["FlowContent", "take2.Parameters"]] + children: List[ + Union[ + "FlowContent", + "take2.Parameters", + "take2.Unimplemented", + "take2.SubstitutionDef", + ] + ] StaticPhrasingContent = Union[ diff --git a/papyri/myst_serialiser.py b/papyri/myst_serialiser.py index ecf2a765..c1e21420 100644 --- a/papyri/myst_serialiser.py +++ b/papyri/myst_serialiser.py @@ -17,56 +17,62 @@ base_types = {int, str, bool, type(None)} + + def serialize(instance, annotation): - if annotation in base_types: - # print("BASE", instance) - assert isinstance(instance, annotation), f"{instance} {annotation}" - return instance + try: + if annotation in base_types: + # print("BASE", instance) + assert isinstance(instance, annotation), f"{instance} {annotation}" + return instance - origin = getattr(annotation, "__origin__", None) - if origin is list: - assert isinstance(instance, origin) - inner_annotation = annotation.__args__ - # assert len(inner_annotation) == 1, inner_annotation - return [serialize(x, inner_annotation[0]) for x in instance] - if origin is dict: - assert isinstance(instance, origin) - key_annotation, value_annotation = annotation.__args__ - # assert key_annotation == str, key_annotation - return {k: serialize(v, value_annotation) for k, v in instance.items()} - if getattr(annotation, "__origin__", None) is Union: - inner_annotation = annotation.__args__ - if len(inner_annotation) == 2 and inner_annotation[1] == type(None): - # assert inner_annotation[0] is not None - # here we are optional; we _likely_ can avoid doing the union trick and store just the type, or null - if instance is None: - return None - else: - return serialize(instance, inner_annotation[0]) - assert ( - type(instance) in inner_annotation - ), f"{type(instance)} not in {inner_annotation}, {instance} or type {type(instance)}" - ma = [x for x in inner_annotation if type(instance) is x] - # assert len(ma) == 1 - ann_ = ma[0] - serialized_data = serialize(instance, ann_) - type_ = ann_.__name__ - if hasattr(ann_, "type"): - type_ = ann_.type - if isinstance(serialized_data, dict): - return {**serialized_data, "type": type_} - return {"data": serialized_data, "type": type_} - if ( - (type(annotation) is type) - and type.__module__ not in ("builtins", "typing") - and (instance.__class__.__name__ == getattr(annotation, "__name__", None)) - or type(instance) == annotation - ): - data = {} - type_ = type(instance).__name__ - if hasattr(instance, "type"): - type_ = instance.type - data["type"] = type_ - for k, ann in gth(type(instance)).items(): - data[k] = serialize(getattr(instance, k), ann) - return data + origin = getattr(annotation, "__origin__", None) + if origin is list: + assert isinstance(instance, origin), f"{instance} {origin}" + inner_annotation = annotation.__args__ + # assert len(inner_annotation) == 1, inner_annotation + return [serialize(x, inner_annotation[0]) for x in instance] + if origin is dict: + assert isinstance(instance, origin) + key_annotation, value_annotation = annotation.__args__ + # assert key_annotation == str, key_annotation + return {k: serialize(v, value_annotation) for k, v in instance.items()} + if getattr(annotation, "__origin__", None) is Union: + inner_annotation = annotation.__args__ + if len(inner_annotation) == 2 and inner_annotation[1] == type(None): + # assert inner_annotation[0] is not None + # here we are optional; we _likely_ can avoid doing the union trick and store just the type, or null + if instance is None: + return None + else: + return serialize(instance, inner_annotation[0]) + assert ( + type(instance) in inner_annotation + ), f"{type(instance)} not in {inner_annotation}, {instance} or type {type(instance)}" + ma = [x for x in inner_annotation if type(instance) is x] + # assert len(ma) == 1 + ann_ = ma[0] + serialized_data = serialize(instance, ann_) + type_ = ann_.__name__ + if hasattr(ann_, "type"): + type_ = ann_.type + if isinstance(serialized_data, dict): + return {**serialized_data, "type": type_} + return {"data": serialized_data, "type": type_} + if ( + (type(annotation) is type) + and type.__module__ not in ("builtins", "typing") + and (instance.__class__.__name__ == getattr(annotation, "__name__", None)) + or type(instance) == annotation + ): + data = {} + type_ = type(instance).__name__ + if hasattr(instance, "type"): + type_ = instance.type + data["type"] = type_ + for k, ann in gth(type(instance)).items(): + data[k] = serialize(getattr(instance, k), ann) + return data + except Exception as e: + e.add_note(f"serializing {instance.__class__}") + raise diff --git a/papyri/render.py b/papyri/render.py index db6adf3e..1eb4eb8b 100644 --- a/papyri/render.py +++ b/papyri/render.py @@ -638,6 +638,8 @@ def render_one( + [DefList([self.LR.visit(s) for s in doc.see_also])], ) # assert False, doc.see_also + if doc.signature: + doc.signature.to_dict() module = qa.split(".")[0] return template.render( current_type=current_type, @@ -656,7 +658,8 @@ def render_one( toctrees=toctrees, ) except Exception as e: - raise ValueError("qa=", qa) from e + e.add_note(f"Rendering with QA={qa}") + raise async def _serve_narrative(self, package: str, version: str, ref: str): """ @@ -1216,10 +1219,18 @@ def replace_Link(self, link: Link): return [MText(link.value + f"({link}?)")] def replace_Section(self, section: Section) -> MRoot: + if section.title is not None: + return [ + MRoot( + [ + MHeading(depth=section.level, children=[MText(section.title)]), + *section.children, + ] + ) + ] return [ MRoot( [ - MHeading(depth=section.level, children=[MText(section.title)]), *section.children, ] ) @@ -1234,7 +1245,8 @@ def replace_SeeAlsoItem(self, see_also: SeeAlsoItem) -> List[DefListItem]: descriptions = [self.visit(d) for d in descriptions] # TODO: this is type incorrect for now. Fix later - print("SAType_", type_) + if type_ is not None: + print("SAType_", type_) return [DefListItem(dt=name, dd=descriptions)] def replace_RefInfo(self, refinfo: RefInfo) -> List[Any]: diff --git a/papyri/signature.py b/papyri/signature.py index 335bcdef..14652274 100644 --- a/papyri/signature.py +++ b/papyri/signature.py @@ -48,6 +48,7 @@ class SignatureNode(Node): kind: str # maybe enum, is it a function, async generator, generator, etc. parameters: List[ParameterNode] # of pairs, we don't use dict because of ordering return_annotation: Union[Empty, str] + type = "signature" def to_signature(self): return inspect.Signature([p.to_parameter() for p in self.parameters]) diff --git a/papyri/take2.py b/papyri/take2.py index 75b78c1f..e6d9bd65 100644 --- a/papyri/take2.py +++ b/papyri/take2.py @@ -161,6 +161,7 @@ class SubstitutionDef(Node): def __init__(self, value, children): print(f"Sdef {value=} {children=}") self.value = value + assert isinstance(children, list) self.children = children pass diff --git a/papyri/templates/html.tpl.j2 b/papyri/templates/html.tpl.j2 index 6e7efad1..ffef42a4 100644 --- a/papyri/templates/html.tpl.j2 +++ b/papyri/templates/html.tpl.j2 @@ -20,6 +20,7 @@ {% if doc.signature -%} + {{ render_myst(doc.signature) }} {{name}}{{doc.signature.to_signature()}} {%- endif -%}