nornir-automation / nornir

Pluggable multi-threaded framework with inventory management to help operate collections of devices
https://nornir.readthedocs.io/
Apache License 2.0
1.37k stars 233 forks source link

WIP: Inventory data plugin #888

Open ubaumann opened 8 months ago

ubaumann commented 8 months ago

This pull request is a quick and incomplete implementation of adding an Inventory Data Plugin. The idea is to have a base to discuss this idea and what would be needed to accept such a change.

At the moment, the inventory data is stored in a Python dictionary. This works great. However, I found myself in a situation where I would like to replace the dictionary with objects. More specifically, I would like to use a pydantic model for the data structure. Firstly, to have input validation and, secondly, to be able to add functions to the pedantic objects to reduce logic in templates.

This PR adds a new plugin type called InventoryDataPlugin. The idea is to allow the default dict object to be replaced with another object. For now, I implemented it in a way that does not break any tests, as the new datatype used to store the data needs to provide methods similar to a dictionary. The code and plugin are most likely more complex because of this.

To use pydantic I would try to implement a plugin to use a model similar to this (just "random" pseudocode, untested):

from typing import (
    Any,
    ItemsView,
    KeysView,
    ValuesView,
)

from pydantic import BaseModel, ConfigDict
from pydantic.networks import IPvAnyAddress

class User(BaseModel):
    model_config = ConfigDict(
        strict=True
    )
    name: str
    age: int
    is_active: bool
    ip: IPvAnyAddress

    def __getitem__(self, key: str) -> Any:
        return getattr(self, key)

    def get(self, key: str, default: Any = None) -> Any:
        try:
            return self.__getitem__(key)
        except AttributeError:
            return default

    def __setitem__(self, key: str, value: Any):
        self.model_validate({**self.__dict__, **{key: value}})
        setattr(self, key, value)

    def keys(self) -> KeysView[str]:
        return self.__dict__.keys()

    def values(self) -> ValuesView[Any]:
        return self.__dict__.values()

    def items(self) -> ItemsView[str, Any]:
        return self.__dict__.items()

The model, of course, would be much more complex with nested models. What already works well is to store multiple models in the data dictionary. So, in this case, only the first level would be a dictionary.

ubaumann commented 8 months ago

PS: The tests are failing as the package is not installed again in the pipeline because of cashing.