I'm trying to create Model class with attrs module. In my script CollectionModel
class is inherited in User
class. So logically all the attributes in the CollectionModel
should be available in the User
. But while trying to create a new instance of User from dictionary (it is possible with attrs) it shows that attributes of CollectionModel
are not present in the User
My Script:
from bson import ObjectId
from attrs import asdict, define, field, validators, Factory
import time
@define
class CollectionModel:
""" Base Class For All Collection Schema"""
_id : str = field(converter=str, default=Factory(ObjectId))
_timestamp : float = field(default=Factory(time.time))
def get_dict(self):
return asdict(self)
def update(self):
self._timestamp = time.time()
@define
class User(CollectionModel):
username : str = field(factory = str, validator=validators.instance_of(str))
userType : str = field(factory = str, validator=validators.instance_of(str))
password : str = field(factory = str, validator=validators.instance_of(str))
user_object = User()
new_user_object = User(**asdict(user_object))
Here, I'm trying to create a new User object from the user_object
. It shows the following error.
TypeError: User.__init__() got an unexpected keyword argument '_id'
I guessed that the parent class is not initiated at first. So I tried to initiate it with the super()
function and according to attrs documentation it should be done with __attrs_pre_init__
. From the documentation:
The sole reason for the existence of
__attrs_pre_init__
is to give users the chance to callsuper().__init__()
, because some subclassing-based APIs require that.
So the modified child class becomes like this.
@define
class User(CollectionModel):
username : str = field(factory = str, validator=validators.instance_of(str))
userType : str = field(factory = str, validator=validators.instance_of(str))
password : str = field(factory = str, validator=validators.instance_of(str))
def __attrs_pre_init__(self):
super().__init__()
But the problem still remains. Am I doing the OOP in the wrong way? Or is it just a bug of attrs module?
CodePudding user response:
I've solved that issue and there were two problems. The first one, in python, underscore before an attribute name makes it a private attribute. So while creating a new instance from dictionary _id
is an unexpected key.
The second problem is, I indeed need an _id
field as I'm working with MongoDB. In MongoDB document _id
is reserved for using as a primary key.
Now the first problem can be solved by replacing _id
to id_
(yes, underscore in the opposite side since id
is literally a bad choice for a variable name in python).
For the second problem I've searched a lot to make an alias for id_
field. But it is not simply easy while working with attrs
module. So I've modified the get_dict()
method to get a perfect dictionary before throwing my data in MongoDB collection.
Here is the modified CollectionModel
class and User
class:
@define
class CollectionModel:
""" Base Class For All Collection Schema"""
id_: ObjectId = field(default=Factory(ObjectId))
timestamp : float = field(default=Factory(time.time))
def get_dict(self):
d = asdict(self)
d["_id"] = d["id_"]
del d["id_"]
return d
def update(self):
self.timestamp = time.time()
@define
class User(CollectionModel):
username : str = field(factory = str, validator=validators.instance_of(str))
userType : str = field(factory = str, validator=validators.instance_of(str))
password : str = field(factory = str, validator=validators.instance_of(str))
Now printing an instance:
user_object = User()
print(user_object.get_dict())
Output:
{'timestamp': 1645131182.421929, 'username': '', 'userType': '', 'password': '', '_id': ObjectId('620eb5ae10f27b87de5be3a9')}