Home > front end >  Change class into dataclass
Change class into dataclass

Time:09-09

I want to refactor a class into a dataclass, here is what I have:

class Tenant:
   def __init__(self, tenant: dict, api_client: str) -> None:
      self.name = tenant["name"]
      self.tenant_id= tenant["id"]
      self.subdomain = tenant["subdomain"]
      self.api_client= api_client

What would be the same class as a dataclass? I tried with something like that, but I don't know how to separate this dict into name, tenant_id and subdomain:

@dataclass
class Tenant:
   tenant: dict
   api_client: str

CodePudding user response:

I would add a class method to the data class to extract the necessary values from a dict.

@dataclass
class Tenant:
    name: str
    tenant_id: int
    subdomain: str
    api_client: str

    @classmethod
    def from_dict(cls, tenant: dict, api_client: str):
        return cls(tenant["name"],
                   tenant["id"],
                   tenant["subdomain"],
                   api_client)


t1 = Tenant("alice", 5, "bar", "client")
t2 = Tenant.from_dict({"name": "bob", "id": 6, "subdomain": "foo"},
                      "client")

I would take the same approach even if Tenant were not a dataclass. An instance of Tenant is only interested in the values to assign to its attributes, not how those values are packaged prior to the instance being created.


If you must preserve the existing API for Tenant, you'll need to use an InitVar and the __post_init__ method.

from dataclasses import dataclass, InitVar, field


@dataclass
class Tenant:
    tenant: InitVar[dict]
    name: str = field(init=False)
    tenant_id: int = field(init=False)
    subdomain: str = field(init=False)
    api_client: str

    # Approximate __init__ method generated
    # def __init__(self, tenant, api_client):
    #     self.api_client = api_client
    #     self.__post_init__(tenant)

    def __post_init__(self, tenant):
        self.name = tenant["name"]
        self.tenant_id = tenant["id"]
        self.subdomain = tenant["subdomain"]

t = Tenant({"name": "bob", "id": 6, "subdomain": "foo"},
           "client")

tenant, as an InitVar, is passed to __init__ and __post_init__, but will not be used as an attribute for the other autogenerated methods. name, tenant_id, and subdomain will not be accepted as arguments to __init__, but will be used by the other autogenerated methods. You, however, are responsible for ensuring they are set correctly in __post_init__.


A possible hybrid approach to define a "private" class, and make the name Tenant refer to the class method.

def _from_dict(cls, tenant, api_client):
    return cls(tenant["name"],
               tenant["id"],
               tenant["subdomain"],
               api_client)

# Using make_dataclass just to make the class name
# 'Tenant' instead of '_Tenant'. You can use an
# ordinary class statement and patch _Teant.__name__
# instead.
_Tenant = dataclasses.make_dataclass(
      'Tenant',
      [('name', str),
       ('tenant_id', int),
       ('subdomain', str),
       ('api_client', str)],
      namespace={'from_dict': classmethod(_from_dict)}
     )

Tenant = _Tenant.from_dict

CodePudding user response:

As @chepner suggested, the idea of implementing a .from_dict method is definely pythonic and readable too, so i'm going to implement it in this code too.

Since the OP wants that the Class must have 2 arguments I would suggest to use a collections.namedtuple.

 collections.namedtuple(typename, field_names, *, rename=False, defaults=None, module=None)¶

Returns a new tuple subclass named typename. The new subclass is used to create tuple-like objects that have fields accessible by attribute lookup as well as being indexable and iterable

WARNING: I haven't tested the code yet

TenantnoAPI = namedtuple("Tenant_noAPI","name tenant_id subdomain")

@dataclass
class Tenant:
    tenantnoapi:TenantnoAPI
    api_client: str

    @classmethod
    def from_dict(cls, tenant: dict, api_client: str):
        return cls(TenantnoAPI(tenant["name"],
                                tenant["id"],
                                tenant["subdomain"]),
                   api_client)
  • Related