1

I'm going through the PyMongo tutorial and there's one thing that I don't understand.

We are shown that we can create a database collection like this:

>>>client = MongoClient()   
>>>print(client)

MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True) 

>>>db = client.test_database
>>>print(db)

Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test_database')

>>>collection = db.test_collection #posts is the collection.
>>>print(collection)

Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'test_database'), 'test_collection')

My initial thought was: "Did they make sure to include a test_database attribute for the client and a test_collection attribute for the database just to make it work with the tutorial?" But further experimentation showed me that I could create databases and collections in this way with any "attribute name" I please! For example:

>>>client = MongoClient()
>>>db = client.foo
>>>print(db)

Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'foo')

>>>collection = db.bar
>>>print(collection)

Collection(Database(MongoClient(host=['localhost:27017'], document_class=dict, tz_aware=False, connect=True), 'foo'), 'bar')

How does this work in Python? I've tried to understand it by reading the pymongo files in the GitHub repository but it's quite difficult for a newbie to understand.

Sahand
  • 7,980
  • 23
  • 69
  • 137

2 Answers2

3

MongoClient overrides a "magic" method, __getattr__. Whenever you access an attribute on a MongoClient object that isn't actually a property or attribute of the object, for example when you access "test_database", the Python interpreter calls:

client.__getattr__("test_database")

The implementation of MongoClient.__getattr__ then creates a Database object and returns it.

Database also overrides __getattr__ to return a Collection with any name.

Both classes also override __getitem__ so that bracketed access works:

client["test_database"]

See the __getattr__ docs here.

A. Jesse Jiryu Davis
  • 23,641
  • 4
  • 57
  • 70
1

You need to implement __getattr__ method in your class to make it work.
Simply speaking - whenever you access an attribute that is not defined in the class, __getattr__ method will be invoked (it's actually a little more complicated). See example below:

class Database(object):

    def __init__(self, host):
        self.host = host

    def __getattr__(self, name):
        return Collection(name)

class Collection(object):

    def __init__(self, name):
        self.name = name

    def __repr__(self):
        return '<Collection name={0}>'.format(self.name)

>>> db = Database('www.example.com')
>>> db.host
'example.com'
>>> db.foo
<Collection name=foo>
>>> db.bar
<Collection name=bar>

You can read more about __getattr__ here: Python: how to implement __getattr__()?

matino
  • 17,199
  • 8
  • 49
  • 58