From 38443f034e67889e18376b92ea9676bb0a4db8bb Mon Sep 17 00:00:00 2001 From: Andrew Mirsky Date: Sat, 12 Jul 2025 11:43:57 -0400 Subject: [PATCH] display 'default_factory' in a field as the actual value --- amqtt/contexts.py | 2 + docs/scripts/exts.py | 116 ++++++++++++++++++ .../python/material/attribute.html.jinja | 2 - .../python/material/expression.html.jinja | 9 +- .../python/material/labels.html.jinja | 3 +- .../python/material/signature.html.jinja | 6 +- mkdocs.rtd.yml | 3 +- 7 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 docs/scripts/exts.py diff --git a/amqtt/contexts.py b/amqtt/contexts.py index 5829810..1458893 100644 --- a/amqtt/contexts.py +++ b/amqtt/contexts.py @@ -31,6 +31,8 @@ class ListenerType(StrEnum): TCP = 'tcp' WS = 'ws' + def __repr__(self) -> str: + return f'"{str(self.value)}"' class Dictable: def __getitem__(self, key): diff --git a/docs/scripts/exts.py b/docs/scripts/exts.py new file mode 100644 index 0000000..b669fb0 --- /dev/null +++ b/docs/scripts/exts.py @@ -0,0 +1,116 @@ +import ast +import json +import pprint +from typing import Any + +import griffe +from _griffe.agents.inspector import Inspector +from _griffe.agents.nodes.runtime import ObjectNode +from _griffe.agents.visitor import Visitor +from _griffe.models import Attribute + +from amqtt.contexts import default_listeners, default_broker_plugins, default_client_plugins + +default_factory_map = { + 'default_listeners': default_listeners(), + 'default_broker_plugins': default_broker_plugins(), + 'default_client_plugins': default_client_plugins() +} + +def get_qualified_name(node: ast.AST) -> str | None: + """Recursively build the qualified name from an AST node.""" + if isinstance(node, ast.Name): + return node.id + elif isinstance(node, ast.Attribute): + parent = get_qualified_name(node.value) + if parent: + return f"{parent}.{node.attr}" + return node.attr + elif isinstance(node, ast.Call): + # e.g., uuid.uuid4() + return get_qualified_name(node.func) + return None + +def get_fully_qualified_name(call_node): + """ + Extracts the fully qualified name from an ast.Call node. + """ + if isinstance(call_node.func, ast.Name): + # Direct function call (e.g., "my_function(arg)") + return call_node.func.id + elif isinstance(call_node.func, ast.Attribute): + # Method call or qualified name (e.g., "obj.method(arg)" or "module.submodule.function(arg)") + parts = [] + current = call_node.func + while isinstance(current, ast.Attribute): + parts.append(current.attr) + current = current.value + if isinstance(current, ast.Name): + parts.append(current.id) + return ".".join(reversed(parts)) + else: + # Handle other potential cases (e.g., ast.Subscript) if necessary + return None + +def get_callable_name(node): + if isinstance(node, ast.Name): + return node.id + elif isinstance(node, ast.Attribute): + return f"{get_callable_name(node.value)}.{node.attr}" + return None + +def evaluate_callable_node(node): + try: + # Wrap the node in an Expression so it can be compiled + expr = ast.Expression(body=node) + compiled = compile(expr, filename="", mode="eval") + return eval(compiled, {"__builtins__": __builtins__, "list": list, "dict": dict}) + except Exception as e: + return f"" + +class MyExtension(griffe.Extension): + def on_instance( + self, + node: ast.AST | griffe.ObjectNode, + obj: griffe.Object, + agent: griffe.Visitor | griffe.Inspector, + **kwargs, + ) -> None: + """Do something with `node` and/or `obj`.""" + if obj.kind == griffe.Kind.FUNCTION and obj.name == 'default_broker_plugins': + print(f"my extension on instance {node} > {obj}") + + def on_attribute_instance( + self, + *, + node: ast.AST | ObjectNode, + attr: Attribute, + agent: Visitor | Inspector, + **kwargs: Any, + ) -> None: + if not hasattr(node, "value"): + return + if isinstance(node.value, ast.Call): + for kw in node.value.keywords: + if kw.arg == "default_factory": + if get_callable_name(kw.value) != "dict": + callable_name = get_callable_name(kw.value) + if callable_name in default_factory_map: + print(default_factory_map[callable_name]) + if "factory" not in attr.extra: + attr.extra["factory"] = {} + attr.extra["factory"]["has_badge"] = True + pprint.pprint(default_factory_map[callable_name]) + f = f"{pprint.pformat(default_factory_map[callable_name], indent=4, width=80, sort_dicts=False)}" + attr.extra["factory"]["eval"] = f + else: + attr.extra["factory"]["has_badge"] = True + attr.extra["factory"]["eval"] = "{}" + + # print(f'callable name: {get_callable_name(kw.value)}') + # # print(evaluate_callable_node(kw.value)) + # print(f'type of node {type(kw.value)}') + # print(f'qualified name: {get_qualified_name(kw.value)}') + # # print(f'fully qualified name: {get_fully_qualified_name(node.value)}') + # factory = evaluate_callable_node(kw.value) + # attribute.extensions["default_factory"] = factory diff --git a/docs/templates/python/material/attribute.html.jinja b/docs/templates/python/material/attribute.html.jinja index 92bd950..4ab9079 100644 --- a/docs/templates/python/material/attribute.html.jinja +++ b/docs/templates/python/material/attribute.html.jinja @@ -3,5 +3,3 @@ {% block logs scoped %}

attribute.html.jinja

{% endblock logs %} - - \ No newline at end of file diff --git a/docs/templates/python/material/expression.html.jinja b/docs/templates/python/material/expression.html.jinja index 289c666..658fb31 100644 --- a/docs/templates/python/material/expression.html.jinja +++ b/docs/templates/python/material/expression.html.jinja @@ -1,8 +1 @@ -{% extends "_base/expression.html.jinja" %} - -{% block logs scoped %} - - << expression.html.jinja >> -{% endblock logs %} - - \ No newline at end of file +{% if 'default_factory' in expression.__str__() %}{{ obj.extra.factory.eval | safe }}{% else %}{{ expression | safe }}{% endif %} \ No newline at end of file diff --git a/docs/templates/python/material/labels.html.jinja b/docs/templates/python/material/labels.html.jinja index 71d46de..e033d8a 100644 --- a/docs/templates/python/material/labels.html.jinja +++ b/docs/templates/python/material/labels.html.jinja @@ -1,7 +1,6 @@ {% extends "_base/labels.html.jinja" %} - {% block logs scoped %} +{% block logs scoped %}

labels.html.jinja

{% endblock logs %} - \ No newline at end of file diff --git a/docs/templates/python/material/signature.html.jinja b/docs/templates/python/material/signature.html.jinja index e4f8ef2..2794fa0 100644 --- a/docs/templates/python/material/signature.html.jinja +++ b/docs/templates/python/material/signature.html.jinja @@ -1,7 +1,7 @@ {% extends "_base/signature.html.jinja" %} - {% block logs scoped %} - << signature.html.jinja >> -{% endblock logs %} +{# {% block logs scoped %}#} +{# << signature.html.jinja >>#} +{#{% endblock logs %}#} \ No newline at end of file diff --git a/mkdocs.rtd.yml b/mkdocs.rtd.yml index abb54c7..f2bd848 100644 --- a/mkdocs.rtd.yml +++ b/mkdocs.rtd.yml @@ -151,7 +151,8 @@ plugins: signature_crossrefs: true summary: true extensions: - + - docs/scripts/exts.py:MyExtension: + paths: [mypkg.mymod.myobj] - git-revision-date-localized: enabled: !ENV [DEPLOY, false] enable_creation_date: true