Magic Methods¶
Note
This part is intended to describe an internal aspect of the library that is only needed by people who want to develop the library itself and not use it if you only want to use the library in your own projects then you may skip this part however if you have some good python experience and want to know the library internally then go ahead and read.
What are python magic methods¶
Magic methods are like normal methods in Python but they are not called
directly by programmers, they are called by python interpreter behind
the scenes each one of them at specific time or in a specific situation,
for example we have __init__
magic method that is called when we create
an object of a class, __del__
which is called when we delete an object
of a class or it gets out of scope.
These methods start with __
and end with it too, so when we name methods
we better not use this syntax.
Note
We will not explain all python magic methods here, to learn more follow this link.
Attribute get and set magic methods¶
There are two magic methods used here the first one is __getattribute__
which is called every time we access an attribute of an object, and
__setattr__
that is called every time we set a value for an attribute.
Look at this code:
class A:
def __getattribute__(self, attr):
try:
return object.__getattribute__(self, attr)
except AttributeError:
return "attribute not found"
def __setattr__(self, attr, value):
object.__setattr__(self, attr, value)
a = A()
a.x = 3
print(a.x)
print(a.y)
In the previous code we used these methods to explain their usage.
Every time we try to set a value for an attribute, as found in line 10
a.x = 3
, the method __setattr__
is called, which calls the
__setattr__
method on the object called object
that is
the parent for all objects in python.
And every time we try to access an attribute value, the method __getattribute__
is called and tries to get the value using the object
object, if
it is not available then it returns the string “attribute not found”, this
is for demonstration purposes only.
Resource class magic methods¶
In our library we have the class Resource
, that is
the parent for all Digital Ocean resources, most of the magic happens in this
class that was written carefully to help us implement other functionality
easily, this class has these two magic methods __getattribute__()
and __setattr__()
.
Here is the code for the __getattribute__
method:
if attr == "resource":
return object.__getattribute__(self, attr)
resource = object.__getattribute__(self, "resource")
static_attrs = resource.static_attrs
dynamic_attrs = resource.dynamic_attrs
fetch_attrs = resource.fetch_attrs
action_attrs = resource.action_attrs
if attr in static_attrs or attr in dynamic_attrs or attr in fetch_attrs:
return self.__fetch(attr)
if attr in action_attrs:
return lambda **kwargs : self.action(type=attr, **kwargs)
return object.__getattribute__(self, attr)
From the previous code we can see that the resource attribute is treated
first, this attribute holds the class of Digital Ocean resource, if we
are using Droplet
then instance would be
Droplet
, this class has a list of class attributes that define the
resource, some of them are static_attrs
, dynamic_attrs
, fetch_attrs
and action_attrs
, we will explain them now.
static_attrs
: These attributes are set by Digital Ocean and cannot be changed directly.dynamic_attrs
: These attributes are set by users and can be changed, they are used when creating resources or updating them.fetch_attrs
: These attributes are set by Digital Ocean and can be used to fetch resources based on them for example: we can fetch a droplet based on its ID.action_attrs
: These are the defined actions for the resource, if a resource does not have any actions associated with it then this list will be empty.
If you check the code again, you can see that if the attribute is one of
fetch_attrs
, static_attrs
or dynamic_attrs
we call __fetch
method, whcih checks if we previously fetched from the API and populated
the object with data, it simply returns the value for the attribute, if not
it calls __do_fetch
that will fetch data from the API.
If the attribute is in action_attrs
, it returns a lambda function
that calls the action()
method with the
correct type.
With this method we transparently call API only when needed, but what
if the value of one attribute changes? How can we detect this and refresh
from the API? The answer is in __setattr__
magic method.
Here is the code for the __setattr__
magic method:
if attr == "resource":
object.__setattr__(self, attr, value)
resource = object.__getattribute__(self, "resource")
static_attrs = resource.static_attrs
dynamic_attrs = resource.dynamic_attrs
fetch_attrs = resource.fetch_attrs
if attr in fetch_attrs:
self.__dict__["__changed"] = attr
self.__dict__["__fetched"] = False
if attr in static_attrs:
return
self.__dict__[attr] = value
From the previous code we can see, if the attribute is in fetch_attrs
then we set the value for __changed
to the name of the attribute
and __fetched
to False, with this way if we try to get the value of
an attribute the class can detect the change and call the API to update.
We can also see that if the attribute is in static_attrs
it returns
without updating its value because these attributes are set by Digital Ocean
and cannot be changed directly.
With these two methods we gave our library the ability to call the API transparently when needed and help users use our classes as they would do with any other classes.