drivendataorg/erdantic

Support Classes with No fields

Closed this issue · 4 comments

This works

from pydantic import BaseModel
class WrapperClass(BaseModel):
    field: int

erd.create(WrapperClass)

but this does not

from pydantic import BaseModel
class EmptyClass(BaseModel):
    pass

erd.create(EmptyClass)
stack trace
StopIteration                             Traceback (most recent call last)
File ~/.pyenv/versions/3.8.11/envs/default/lib/python3.8/site-packages/IPython/core/formatters.py:344, in BaseFormatter.__call__(self, obj)
    342     method = get_real_method(obj, self.print_method)
    343     if method is not None:
--> 344         return method()
    345     return None
    346 else:

File ~/.pyenv/versions/3.8.11/envs/default/lib/python3.8/site-packages/erdantic/core.py:712, in EntityRelationshipDiagram._repr_png_(self)
    710 def _repr_png_(self) -> bytes:
    711     """IPython special method to display object as a PNG image."""
--> 712     graph = self.to_graphviz()
    713     return graph.draw(prog="dot", format="png")

File ~/.pyenv/versions/3.8.11/envs/default/lib/python3.8/site-packages/erdantic/core.py:644, in EntityRelationshipDiagram.to_graphviz(self, graph_attr, node_attr, edge_attr)
    640 g.edge_attr.update(edge_attr or {})
    641 for full_name, model_info in self.models.items():
    642     g.add_node(
    643         full_name,
--> 644         label=model_info.to_dot_label(),
    645         tooltip=model_info.description.replace("\n", "
"),
    646     )
    647 for edge in self.edges.values():
    648     g.add_edge(
    649         edge.source_model_full_name,
    650         edge.target_model_full_name,
   (...)
    654         arrowtail=edge.source_dot_arrow_shape(),
    655     )

File ~/.pyenv/versions/3.8.11/envs/default/lib/python3.8/site-packages/erdantic/core.py:302, in ModelInfo.to_dot_label(self)
    294 """Returns the DOT language "HTML-like" syntax specification of a table for this data
    295 model. It is used as the `label` attribute of data model's node in the graph's DOT
    296 representation.
   (...)
    299     str: DOT language for table
    300 """
    301 # Get number of columns dynamically from first row
--> 302 num_cols = next(iter(self.fields.values())).to_dot_row().count("<td")
    303 # Concatenate DOT of all rows together
    304 rows = "\n".join(field_info.to_dot_row() for field_info in self.fields.values()) + "\n"

StopIteration: 

Hi @xaellison, can you explain a little more about your use case here?

I'm not sure about whether drawing a class with no fields makes sense, but at least there's room for either a better error message or more graceful handling of the error.

Hey @jayqi, thanks for the reply. I'm collaborating on a project that has some placeholder class definitions with no fields - we're going to implement them later.

I don't particularly care about being able to render an empty class. It'd be great if it did not error out, though.

  1. Would it be possible to treat it like a primitive type like an int that shows up as a field but doesn't point anywhere?
  2. Could we populate a fake field in if there are no real ones?

Should be fixed in v1.0.3.

great thank you!