Skip to content

Instantly share code, notes, and snippets.

@asdf8601
Last active July 2, 2024 14:59
Show Gist options
  • Save asdf8601/de4f75143ddfed1467945cf7a006b4a1 to your computer and use it in GitHub Desktop.
Save asdf8601/de4f75143ddfed1467945cf7a006b4a1 to your computer and use it in GitHub Desktop.
[mermaid] classDiagram builder from python code
r"""
This requires to have the library (pkg) pre-installed.
Usage:
$ python ./py2mermaid.py --pkg pandas
$ python ./py2mermaid.py --pkg pandas --base-module pandas.core
$ python ./docs/diagrams/py2mermaid.py --pkg pandas --base-module pandas.core
$ python ./docs/diagrams/py2mermaid.py --pkg pandas --base-module pandas.core --raw | sed "s/\w\.\(\w\)/\U\1/g"
Note: based on https://gist.github.com/Zulko/e0910cac1b27bcc3a1e6585eaee60121
"""
import inspect
import re
from itertools import filterfalse
CLS_TPL = """classDiagram
linkStyle default interpolate basis
{links}
"""
EXCLUDE_METH = [
"_abc_",
"__class__",
"__delattr__",
"__dict__",
"__dir__",
"__doc__",
"__eq__",
"__format__",
"__ge__",
"__getattribute__",
"__gt__",
"__hash__",
"__init_subclass__",
"__le__",
"__lt__",
"__module__",
"__ne__",
"__new__",
"__reduce__",
"__reduce_ex__",
"__repr__",
"__setattr__",
"__sizeof__",
"__str__",
"__subclasshook__",
"__weakref__",
"__abstractmethods__",
]
def class_name(cls):
"""Return a string representing the class"""
# NOTE: can be changed to str(class) for more complete class info
out = "%s.%s" % (cls.__module__, cls.__name__)
return out
def classes_tree(module, base_module=None):
if base_module is None:
base_module = module.__name__
module_classes = set()
module_attrs = []
inheritances = []
def inspect_class(cls):
if class_name(cls) not in module_classes:
if cls.__module__.startswith(base_module):
module_classes.add(class_name(cls))
module_attrs.append(classes_attrs(cls))
for base in cls.__bases__:
inheritances.append((class_name(base), class_name(cls)))
inspect_class(base)
classes_list = [e for e in module.__dict__.values() if inspect.isclass(e)]
for cls in classes_list:
inspect_class(cls)
return module_classes, inheritances, module_attrs
def get_attrs(module_classes, module_attrs):
attrs_list = []
for i, cls in enumerate(module_classes):
mmd = ["%s: +%s" % (cls, attr) for attr in module_attrs[i]]
attrs_list.append("\n".join(mmd))
return attrs_list
def classes_tree_to_mermaid(module_classes, inheritances, module_attrs):
attrs = get_attrs(module_classes, module_attrs)
classes = list(module_classes)
members = ["%s <|-- %s" % (a, b) for a, b in inheritances]
out = CLS_TPL.format(links="\n".join(classes + members + attrs))
return out
def classes_attrs(cls, exclude=EXCLUDE_METH):
all_attr = dir(cls)
def is_exclude(elem):
for ex in exclude:
if ex in elem:
return True
# return False
def function_formatter(elem):
if hasattr(getattr(cls, elem), "__call__"):
return "%s()" % elem
return elem
out = filterfalse(is_exclude, all_attr)
out = map(function_formatter, out)
return list(out)
def remove_dots(mermaid_str):
meth_regex = r"\.(\w)"
def callback(pttn):
return pttn.group(1).title()
class_diagram_mod = re.sub(meth_regex, callback, mermaid_str)
return class_diagram_mod
def cli():
import argparse
parse = argparse.ArgumentParser()
parse.add_argument("--pkg", required=True)
parse.add_argument("--base-module", default=None)
parse.add_argument("--raw", action="store_true")
args = parse.parse_args()
_pkg = args.pkg
base_module = args.base_module
raw = args.raw
pkg = __import__(_pkg)
m_classes, inheritances, m_attrs = classes_tree(
pkg, base_module=base_module
)
class_diagram = classes_tree_to_mermaid(m_classes, inheritances, m_attrs)
if raw:
print(class_diagram)
else:
print(remove_dots(class_diagram))
if __name__ == "__main__":
cli()
@asdf8601
Copy link
Author

Thank you for reporting this; it was indeed peculiar. The problem resided in the examples where I used sed with \U, which I believe was causing this error. I've already made the fix ;-)

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