Reputation: 187
I know similar questions have been previously answered, but after spending hours browsing the web, I still cannot the right solution to my problem (or the 'python-way' solution)
I'm using the following 'database' module to insert any object into MongoDB:
from pymongo import MongoClient
import json
def insert(obj, collection_name):
with MongoClient(27017, 'localhost') as client:
if hasattr(obj, '__dict__'):
doc = obj.__dict__
else:
doc = json.dumps(obj)
return client['test'][collection_name].insert_one(doc).inserted_id
def get_one(collection_name, query):
with MongoClient(27017, 'localhost') as client:
return client['test'][collection_name].find_one(query)
This part works well as I can handle any class in any module and store the objects in a generic way.
The issue I'm facing is to reload an object from MongoDB and cast it back to the correct class.
I have a generic class 'Operation' (representing an action that can be done to bank/money account:
import database
class Operation(object):
def __init__(self):
self._id = 'OPE-{}'.format(str(uuid.uuid1()))
@property
def id(self):
return self._id
def save(self):
database.update(self, 'operations')
def create_from_dict(d):
operation = Operation()
for key, value in d.items():
operation.__dict__[key] = value
return operation
def get(operation_id):
return create_from_dict(database.get_one('operations', {'_id': operation_id}))
Until this point everything works, I can instantiate any Operation object, insert it into MongoDB and load it back using it's id.
Now I have two new classes 'Fee' and 'Credit' derived from 'Operation'
import uuid
from operation import Operation
class Credit(Operation):
def __init__(self):
Operation.__init__(self)
self._id = 'CRE-{}'.format(uuid.uuid1())
@property
def id(self):
return self._id
And
import uuid
from operation import Operation
class Fee(Operation):
def __init__(self):
Operation.__init__(self)
self._id = 'FEE-{}'.format(uuid.uuid1())
@property
def id(self):
return self._id
The idea is to have keep the code to save/load objects in the 'Operation' class and create simple derived class whenever needed. So the 'Operation' class should be able to cast dynamically the loaded object from Operation to Credit/Fee/Transfer...
What I tried to do is add the following line on all classes in order to store the object type:
self.type = self.__class__
Or alternatively (since the class cannot be directly serialized to json)
self.type = self.__name__
But I don't know how I could achieve the :
def create_from_dict(d):
operation = Operation()
for key, value in d.items():
operation.__dict__[key] = value
operation.__class__ = get_type_from_name(operation.type) #This is the function I'm trying to implement
return operation
Should I store both the module and the class name when encoding the object to cast it back from another module?
Sorry for the quite long question but I wanted to fully describe the logic behind my code I'm definitely quite open at any suggestion!
EDIT: Could locals() or globals() be the way to go? You pass the class name to the get_type_from_name() method and it returns the corresponding class? Additional question for advanded Pythonists: is that the right approach?
Upvotes: 1
Views: 1178
Reputation: 187
I think I managed to make it work:
Adding this to the classes I want to (de)serialize:
self.type = self.__class__.__name__
self.module = self.__module__
Then once the json doc is loaded from PyMongo:
if not hasattr(doc, 'module') or not hasattr(doc, 'type'):
return obj
for key, value in doc.items():
obj.__dict__[key] = value
mod = __import__(obj.module, fromlist=[obj.type])
obj.__class__ = getattr(mod, obj.type)
The last two lines import the module containing the class and modify the object class dynamically.
Thanks user3030010 for your answer. But I'm still very curious to know if you guys think this a good design or that could lead to more issues.
Upvotes: 0
Reputation: 1857
Using the self.type = self.__name__
idea you suggested, you could try this implementation for the get_type_from_name
function:
def get_type_from_name(name):
name_to_type = {'Credit': Credit, 'Fee':Fee}
return name_to_type[name]
I'm not quite sure that I fully understand your problem though, so let me know if this isn't what you want.
Upvotes: 1