1

I have two dataclasses inside bookmodel, one inherited from the other:

@dataclass(frozen=True)
class BookOrder:
    category: str
    name: str
    color: str
    price: int
    volume: int

@dataclass(frozen=True)
class ClientOrder(BookOrder):
    client_id: str

Then in another .py file, I need to init a ClientOrder instance using BookOrder:

from book_model import BookOrder
# ...

@dataclass
class Client:
    id: str
    def place_book_order(self, book_order: BookOrder):
        # want to init a ClientOrder HERE!!!

Since BookOrder is NOT callable, I cannot pass self.id to it. I was wondering if there's an elegant way to achieve this?

Update

Guess what I am trying to ask is, is there any other way to initialize a ClientOrder using BookOrder other than the below method?

client_order=ClientOrder(book_order.categry, book_order.name, ..., self.client_id)
demid
  • 348
  • 5
  • 16
  • I'd probably just not use inheritance, and instead make a `ClientOrder` a class that has two members: a `BookOrder` instance and a `client_id`. They you can just pass both to the `ClientOrder` initializer to create a client order. – Carcigenicate Oct 31 '21 at 14:41
  • Can you elaborate on “BookOrder is not callable”? If I understand, classes, like functions, should be callable. – rv.kvetch Oct 31 '21 at 14:43
  • 1
    @rv.kvetch class `BookOrder` does not have input params like `ClientOrder` does right? So I guess what I mean by not callable is that we cannot pass a `id`/any other vars as parameters to add as attribute to BookOrder. – demid Oct 31 '21 at 14:47
  • What’s to stop you from creating a method like `place_client_order` which could be used to create a ClientOrder object? – rv.kvetch Oct 31 '21 at 15:00
  • 1
    @rv.kvetch Idk but when I init using this format `client_order=ClientOrder(book_order)` I got `TypeError: __init__() missing 5 required positional arguments: 'name', 'color', 'price', 'volume', and 'client_id'`. Does this mean I have to initialize it by specfing each attributes like `client_order=ClientOrder(book_order.categry, book_order.name, ..., self.client_id)`? – demid Oct 31 '21 at 15:24

1 Answers1

1

One way to solve this is by not using inheritance, and instead declaring client_id as an optional attribute instead. Since the BookOrder dataclass is also frozen, we then need to call object.__setattr__ to modify the value of client_id, as mentioned in this post.

In your first module a.py:

from __future__ import annotations  # Added for compatibility with 3.7+

from dataclasses import dataclass


@dataclass(frozen=True)
class BookOrder:
    category: str
    name: str
    color: str
    price: int
    volume: int
    client_id: str | None = None

In second module b.py:

from book_model import BookOrder
# ...

@dataclass
class Client:
    id: str
    def place_book_order(self, book_order: BookOrder):
        # The following does not work, because BookOrder is a frozen
        # dataclass.
        # setattr(book_order, 'client_id', self.id)
        # Use object.__setattr__ as mentioned here:
        #  https://stackoverflow.com/a/59249252/10237506
        object.__setattr__(book_order, 'client_id', self.id)

        if book_order.client_id:
            print('Successfully set client_id attribute:',
                  repr(book_order.client_id))
        else:
            print('Was unable to set client_id attribute on frozen dataclass')


Client('abc123').place_book_order(BookOrder(*['a'] * 5))

Outputs:

Successfully set client_id attribute: 'abc123'

Of course, the easier way is to just not define it as frozen dataclass:

@dataclass
class BookOrder:
    category: str
    name: str
    color: str
    price: int
    volume: int
    client_id: str | None = None

And then the only change needed in b.py:

    ...

    def place_book_order(self, book_order: BookOrder):
        book_order.client_id = self.id
        ...
rv.kvetch
  • 9,940
  • 3
  • 24
  • 53
  • 1
    I see. Thanks for the detailed answer! (Have to use `frozen` plus inherited `ClientOrder` class is pre-defined so that's why I'm so lost haha) – demid Oct 31 '21 at 15:36
  • No problem! I get what you were trying to do and it also would've been possible to (dynamically) create a `ClientOrder` object using the fields from a BookOrder, but I think with this approach it is overall a bit simpler. – rv.kvetch Oct 31 '21 at 15:38