3

Introduction

Hello everyone !

I am trying to develop multi-agent models in Python3. So my approach is to create basic classes and derive them to more concrete and specific ones. For instance, a class Bike inherits from Vehicle, itself inheriting from a basic Agent class.

Problem

I want to offer a clear specification of my classes init parameters using JSON Schema (and also use them for validation), but I am struggling to automate their generation. Let's look at an example:

class Agent:
    
    SCHEMA = {
        "properties": {
            "agent_id": {
                "type": "string",
                "description": "unique identifier"
            },
            "network": {
                "type": "string",
                "description": "road network used by the agent to move"
            },
            "origin": {
                "type": "integer",
                "description": "origin position id",
            },
            "icon": {
                "type": "string",
                "description": "display icon"
            }
        },
        "required": ["agent_id", "network", "origin", "icon"]
    }
    
    def __init__(self, agent_id, network, origin, icon):
        self.id = agent_id
        self.network = network
        self.position = origin
        self.icon = icon
        
    def move(self, position):
        self.position = position


class User(Agent):
    SCHEMA = {
        # that's what i want in the end, but i don't want to duplicate the common properties
        "properties": {
            "agent_id": {
                "type": "string",
                "description": "unique identifier"
            },
            # notice that there is no "network" property
            "origin": {
                "type": "integer",
                "description": "origin position id",
            },
            "destination": {
                "type": "integer",
                "description": "destination position id",
            },
            "icon": {
                "type": "string",
                "description": "display icon"
            }
        },
        "required": ["agent_id", "origin", "destination", "icon"]
    }

    def __init__(self, agent_id, origin, destination, icon="user"):
        super().__init__(agent_id, "walk", origin, icon)
        self.destination = destination


class Vehicle(Agent):
    
    SCHEMA = {
        # one way to inherit the schema could be like this, but it has its flaws
        **super().SCHEMA,
        "seats": {
            "type": "integer",
            "description": "capacity of the vehicle"
        }
    }
    
    def __init__(self, agent_id, network, origin, seats, icon):
        super().__init__(agent_id, network, origin, icon)
        self.seats = seats
      
        
class Bike(Vehicle):

    # i want a schema here too, but without the "seats" prop
    # and maybe specify the default value for "icon" ?

    def __init__(self, agent_id, origin, icon="bike"):
        super().__init__(agent_id, "bike", origin, icon, 1)
        
        
class Car(Vehicle):
    
    # quite same question here

    def __init__(self, agent_id, origin, seats, icon="car"):
        super().__init__(agent_id, "drive", origin, icon, seats)

As you can see, I want to write and add specifications as I add new parameters to my classes. I would like to re-use the schemas from higher classes in order to reduce code duplication, but it's hard. I have some ideas, for instance what I proposed above, but it does not allow stripping a parameter schema from the parent class for instance.. Maybe using a method would give me more control on how the schemas are built.

The question

I would like to know if there is a library that allows doing this, or, otherwise, I would be glad to have some advice on how to accomplish this.

  • If you are not using SCHEMA for any logic, I think a standard PeP-8 documentation would be good enough! – Kris Oct 18 '21 at 14:27
  • 1
    I would like to use json schemas for input validation and also for generating forms in a web application that would be used for running the models. I find json schemas very practical, as i can use them for specification, validation, documentation, and export them for any other use I may have ! – LeoDebTellae Oct 18 '21 at 14:59

1 Answers1

0

Pydantic can help you achieve this:

Example from the Pydantic documentation

from enum import Enum
from pydantic import BaseModel, Field


class FooBar(BaseModel):
    count: int
    size: float = None


class Gender(str, Enum):
    male = 'male'
    female = 'female'
    other = 'other'
    not_given = 'not_given'


class MainModel(BaseModel):
    """
    This is the description of the main model
    """

    foo_bar: FooBar = Field(...)
    gender: Gender = Field(None, alias='Gender')
    snap: int = Field(
        42,
        title='The Snap',
        description='this is the value of snap',
        gt=30,
        lt=50,
    )

    class Config:
        title = 'Main'


# this is equivalent to json.dumps(MainModel.schema(), indent=2):
print(MainModel.schema_json(indent=2))

Output:

# this is equivalent to json.dumps(MainModel.schema(), indent=2):
print(MainModel.schema_json(indent=2))

{
  "title": "Main",
  "description": "This is the description of the main model",
  "type": "object",
  "properties": {
    "foo_bar": {
      "$ref": "#/definitions/FooBar"
    },
    "Gender": {
      "$ref": "#/definitions/Gender"
    },
    "snap": {
      "title": "The Snap",
      "description": "this is the value of snap",
      "default": 42,
      "exclusiveMinimum": 30,
      "exclusiveMaximum": 50,
      "type": "integer"
    }
  },
  "required": [
    "foo_bar"
  ],
  "definitions": {
    "FooBar": {
      "title": "FooBar",
      "type": "object",
      "properties": {
        "count": {
          "title": "Count",
          "type": "integer"
        },
        "size": {
          "title": "Size",
          "type": "number"
        }
      },
      "required": [
        "count"
      ]
    },
    "Gender": {
      "title": "Gender",
      "description": "An enumeration.",
      "enum": [
        "male",
        "female",
        "other",
        "not_given"
      ],
      "type": "string"
    }
  }
}
Fontanka16
  • 1,161
  • 1
  • 9
  • 37
  • Also, there are libraries like pyhump that can help you with the casing between JSON/POJO:s – Fontanka16 Dec 01 '21 at 16:48
  • Thank you for your answer ! I had a look at Pydantic, but it seemed to me that I had to create a separate Pydantic data model for each one of my agent classes, which seemed a lot of duplicated code. And I don't really get how this would work with inherited classes. But maybe I don't understand Pydantic very well, since most of their examples don't include actual code, just the data models. And I do need code in my classes – LeoDebTellae Dec 08 '21 at 10:54