Reputation: 1394
My main class is defined as follows:
from pymongo import MongoClient
from credentials import MongoMarketPlaceAuth
client = MongoClient('mongodb://conn_url:conn_port/', ssl=True)
db_connection = client.DB_NAME
db_connection.authenticate(name=MongoMarketPlaceAuth.name, password=MongoMarketPlaceAuth.password)
MarketPlace = db_connection
class MarketPlaceObjects(object):
def __init__(self, collection, fields={}, default_filter={}):
self.collection_name = collection
self.fields = fields
self.default_filter = default_filter
if MarketPlace[collection].count() == 0:
raise Exception('Collection does not exist or has zero objects')
self.objects = MarketPlace[collection]
I then have a class that inherits my main class:
class Products(MarketPlaceObjects):
def __init__(self):
super(Products, self).__init__(collection='FlatProduct')
When used like so:
from products import Products
p = Products()
p.objects.find_one()
A dictionary is returned that describes all aspects of the product. What I would like to do is figure out is how
p.objects.find_one()
instead of returning a dictionary or a list of dictionaries it can return either one Product object (from a single returned dictionary) or a list of Product objects (from a returned list of dictionaries).
I'm having a hard time since I'm unsure how to I can wrap the find_one() or find() methods of PyMongo's collection class with my own Product class.
UPDATE (2017-07-25): This is what I ended up doing. Still needs some optimization:
Generic classes for the marketplace:
class CollectionObjectInstance(object):
def __init__(self, response):
for key, value in response.items():
if isinstance(value, dict):
self.__dict__[key] = CollectionObjectInstance(value)
else:
self.__dict__[key] = value
class CollectionObjectsManager(object):
serializer = CollectionObjectInstance
collection = None
default_projection = {}
default_filter = {}
def __init__(self, **kwargs):
self.__dict__ = kwargs
def find(self, filter={}, projection={}):
filter = self.default_filter.update(filter)
projection = self.default_projection.update(projection)
res = self.collection.find(filter, projection)
for o in res:
yield self.serializer(o)
def find_one(self, filter={}, projection={}):
filter = self.default_filter.update(filter)
projection = self.default_projection.update(projection)
res = self.collection.find_one(filter, projection)
return self.serializer(res)
class MarketPlaceCollection(object):
collection_name = None
serializer = None
objects_manager = CollectionObjectsManager
def __init__(self, *args, **kwargs):
if self.collection_name is None:
raise Exception('collection_name must be defined in class')
if self.serializer is None:
raise Exception('serializer must be defined in class')
collection = MarketPlace[self.collection_name]
if collection.count() == 0:
raise Exception('Collection does not exist or has zero objects')
self.collection = collection
self.objects = self.objects_manager(**self.__dict__, **self.__class__.__dict__)
Product implementation using inheritance:
from marketplace import MarketPlaceCollection, CollectionObjectInstance
from settings import BASE_URL
URL_SUFFIX = 'product/'
CASH_PRICE_FEE = 50
LEASE_TERM_WEEKS = 52
class Product(CollectionObjectInstance):
def url(self):
url = BASE_URL + URL_SUFFIX + self.slug
return url
class Products(MarketPlaceCollection):
collection_name = 'FlatProduct'
serializer = Product
Upvotes: 0
Views: 728
Reputation: 110440
When you call p.objects
you get the collection list itself,as attributed on the line self.objects = MarketPlace[collection]
. Your Products
have no control over methods or attributes inside the .objects
attribute anymore - it is an object as returned by pymongo.
So, to have control over methods and attributes of Products.objects
, you have to create another class, with your desired methods, and return an object of that class when one tries to retrieve Products.objects
.
Although Python has the "property" decorator and the descriptor protocol, and a more sophisticated automation of your objects
attribute could make use of them, in this case it can be made in a very straightforward way. Just have another class that receives the collection, and proxies the other attributes to the collection, by implementing __getattr__
in it:
class ObjectList(object):
def __init__(self, collection, cls):
self.collection = collection
self.cls = cls
def find_one(self, *args, **kw):
raw_list = self.collection.find_one(*arg, **kw)
objects = [self.cls(**obj) for obj in raw_list]
return objects[0] if len(objects) == 1 else objects
def __getattr__(self, attr):
"""this is called automatically by Python when a
normal attribute is not found in this object
"""
return getattr(self.collection, attr)
class MarketPlaceObjects(object):
def __init__(self, collection, cls, fields=None, default_filter=None):
self.collection_name = collection
self.fields = fields if fields else {}
self.default_filter = default_filter if defaut_filter else {}
if MarketPlace[collection].count() == 0:
raise Exception('Collection does not exist or has zero objects')
self.objects = ObjectList(MarketPlace[collection], cls)
class Products(MarketPlaceObjects):
def __init__(self):
super(Products, self).__init__(collection='FlatProduct', cls=Product)
Upvotes: 1