Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create an optional field in a dataclass that is inherited?

from typing import Optional

@dataclass
class Event:
   id: str
   created_at: datetime
   updated_at: Optional[datetime]
   #updated_at: datetime = field(default_factory=datetime.now)  CASE 1
   #updated_at: Optional[datetime] = None                       CASE 2

@dataclass
class NamedEvent(Event):
  name: str

When creating an event instance I will generally not have an updated_at field. I can either pass the current time as default value or add a value to it when making the insertion in the database and fetch it in subsequent uses of the object. Which is the better way? As per my understanding, I cannot create a NamedEvent instance without passing the updated_at field in case1 and case2 since I do not have a default value in name field.

like image 329
sparkstar Avatar asked Apr 30 '20 11:04

sparkstar


People also ask

Can a Dataclass inherit?

In this post, we will discuss how DataClasses behave when inherited. Though they make their own constructors, DataClasses behave pretty much the same way as normal classes do when inherited.

Can Dataclass have methods?

dataclasses has a special method called __post_init__ . As the name clearly suggests, this method is called right after the __init__ method is called. Going back to the previous example, we can see how this method can be called to initialize an internal attribute that depends on previously set attributes.

What is __ Post_init __?

The __post_init__ method is called just after initialization. In other words, it is called after the object receives values for its fields, such as name , continent , population , and official_lang .

What is @dataclass in Python?

dataclass module is introduced in Python 3.7 as a utility tool to make structured classes specially for storing data. These classes hold certain properties and functions to deal specifically with the data and its representation.


1 Answers

The underlying problem that you have seems to be the same one that is described here. The short version of that post is that in a function signature (including the dataclass-generated __init__ method), obligatory arguments (like NamedEvent's name) can not follow after arguments with default values (which are necessary to define the behavior of Event's updated_at) - a child's fields will always follow after those of its parent.

So either you have no default values in your parent class (which doesn't work in this case) or all your child's fields need default values (which is annoying, and sometimes simply not feasible).

The post I linked above discusses some patterns that you can apply to solve your problem, but as a nicer alternative you can also use the third part party package pydantic which already solved this problem for you. A sample implementation could look like this:

import pydantic
from datetime import datetime


class Event(pydantic.BaseModel):
    id: str
    created_at: datetime = None
    updated_at: datetime = None

    @pydantic.validator('created_at', pre=True, always=True)
    def default_created(cls, v):
        return v or datetime.now()

    @pydantic.validator('updated_at', pre=True, always=True)
    def default_modified(cls, v, values):
        return v or values['created_at']


class NamedEvent(Event):
    name: str

The default-value specification through validators is a bit cumbersome, but overall it's a very useful package that fixes lots of the shortcomings that you run into when using dataclasses, plus some more.

Using the class definition, an instance of NamedEvent can be created like this:

>>> NamedEvent(id='1', name='foo')
NamedEvent(id='1', created_at=datetime.datetime(2020, 5, 2, 18, 50, 12, 902732), updated_at=datetime.datetime(2020, 5, 2, 18, 50, 12, 902732), name='foo')
like image 140
Arne Avatar answered Oct 18 '22 16:10

Arne