Skip to content

Repair model attribute typing and docstrings #666

@davidorme

Description

@davidorme

With the change to the static model mechanism (#373), model attributes are now largely being defined in the _setup method, which means that the actual model class documentation is missing the model attributes: they are only picked up from __init__ or from class attributes.

We need to fix this - our docs are broken until we do. From @dalonsoa's comments on Teams, we have two clean choices.

  1. We start using __init__ again in the models. It's banned at the moment - only the BaseModel.__init__ gets run and it can't be redefined. This is because __init__ shouldn't do anything but setup the static/non_static model mode and all actual stuff that needs to happen is isolated in _setup, so it can conditionally executed based on the model mode. But we can just agree that actual models __init__ methods should be "logic-free" and are used simply to maintain the documentation and typing of model attributes. They have to call super().__init__().

  2. We document and type all of the model attributes as if they were class attributes (below the class ModelName line and outside of any method). We then don't have to use __init__.

I prefer option (1). Although it is a bit more verbose, it follows the canonical Python practice of declaring instance attributes in __init__. These are attributes that differ between instances, which is true for all of these attributes. In option 2, we are pretending they are class attributes, which should be things that are fixed across all class instances. The only reason we can do this is because we don't actually want to assign values to the attributes at a class level, only at an instance level.

The code below shows the two options in a basic setup. The issue we are trying to fix is that attribute eee defined in _setup is not documented in sphinx (and I don't think can be), and nearly all our model attributes are now in this state.

from abc import ABC


class Base(ABC):
    """Base class docstring."""

    base_class_attr: int
    "A class attribute"

    def __init__(self) -> None:
        """Base init docstring."""
        self.aaa: int
        "An integer"
        self.bbb: str
        "A string"


class Actual(Base):
    """Actual class docstring."""

    def __init__(self) -> None:
        """Actual init docstring."""
        super().__init__()

        self.ccc: int
        "Another integer"

        self.ddd: str
        "Another string"

    def _setup(self) -> None:
        """Actual setup docstring."""

        self.eee: int
        "This attribute is not documented"


class Base2(ABC):
    """Base2 class docstring."""

    base_class_attr: int
    "A class attribute"

    def __init__(self) -> None:
        """Base2 init docstring."""
        self.aaa: int
        "An integer"
        self.bbb: str
        "A string"


class Actual2(Base):
    """Actual2 class docstring."""

    ccc: int
    "Another integer"

    ddd: str
    "Another string"

    def _setup(self) -> None:
        """Actual2 setup docstring."""

        self.eee: int
        "This attribute is not documented"

This can be built in an API doc using:

.. automodule:: virtual_ecosystem.test
    :autosummary:
    :members:
    :inherited-members: # needed to get inherited members in autosummary
    :special-members: __init__
    :private-members:

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions