The __call__ magic method
The __call__ magic method is special in context to Python metaclasses. Unlike the __init__ method, which gets called when we create a new class from our metaclass, the __call__ method is called when the object of the initialized class is created. To better understand this, let's try to run the following example:
class ExampleMeta(type):
def __init__(cls, name, bases, dct):
print("__init__ called")
return super().__init__(name, bases, dct)
def __call__(cls, *args, **kwargs):
print("__call__ called")
return super().__call__(*args, **kwargs)
class Example(metaclass=Example):
def __init__(self):
print("Example class")
__init__ called
>>> obj = Example()
__call__ called
From this example, it is clear that the __init__ method is called once the interpreter has completed the initialization of a class based on the metaclass and the __call__ method is called when the object of the class is created.
Now, with this understanding in place, let's build our database connection class, which will provide the hold of our database operations. In this example, we will just focus on the initialization part of the class, while providing the complete class implementation details in the later chapters.
Now, under the bugzot directory, let's create a file named database.py which will hold our database class:
from bugzot.meta import Singleton
class Database(metaclass=Singleton):
def __init__(self, hostname, port, username, password, dbname, **kwargs):
"""Initialize the databases
Initializes the database class, establishing a connection with the database and providing
the functionality to call the database.
:params hostname: The hostname on which the database server runs
:parms port: The port on which database is listening
:params username: The username to connect to database
:params password: The password to connect to the database
:params dbname: The name of the database to connect to
"""
self.uri = build_uri(hostname, port, username, password, dbname)
#self.db = connect_db()
self.db_opts = kwargs
#self.set_db_opts()
def connect_db(self):
"""Establish a connection with the database."""
pass
def set_db_opts(self):
"""Setup the database connection options."""
pass
In this example, we have defined the database class that will help us establish a connection to the database. The different thing about this class is, whenever we try to create a new instance of this class, it will always return the same object. For example, let's try to see what happens if we create two different objects of this same class:
dbobj1 = Database("example.com", 5432, "joe", "changeme", "testdb")
dbobj2 = Database("example.com", 5432, "joe", "changeme", "testdb")
>>> dbobj1
<__main__.Database object at 0x7fb6d754a7b8>
>>> dbobj2
<__main__.Database object at 0x7fb6d754a7b8>
In this example, we can see that the same instance of the database object was returned when we tried to instantiate a new object of the class.
Now, let's take a look at one other interesting pattern, known as the Factory pattern.