Goodies of the Python Standard Library¶

json for encoding and decoding JSON¶

Because the web is filled with JSON nowadays and the good days of xml are gone.

In [1]:
data = {"b": True, "a": 1, "nested": {"foo": "bar"}, "c": None, "some_list": [1, 2, 3]}

Encoding¶

In [2]:
import json

json_data = json.dumps(data)
print(f"type: {type(json_data)} data: {json_data}")
type: <class 'str'> data: {"b": true, "a": 1, "nested": {"foo": "bar"}, "c": null, "some_list": [1, 2, 3]}

Decoding¶

In [3]:
decoded = json.loads(json_data)
print(f"type: {type(decoded)} data: {decoded}")
type: <class 'dict'> data: {'b': True, 'a': 1, 'nested': {'foo': 'bar'}, 'c': None, 'some_list': [1, 2, 3]}

unittest.mock¶

Although pytest is the preferred testing framework, unittest.mock module offers some goodies that are helpful also in pytest test cases. Mocking and patching are generally useful for "faking" some parts of the logic/state of the software under test. Common use cases are, for example, patching parts of code that interact with 3rd parties (e.g. some web services).

MagicMock¶

In general, Mocks are simulated objects that replace the functionality/state of a real world object in a controlled way. Thus, they are especially useful in tests for mimicing some behavior of a specific part of the implementation under test.

There is also a Mock class in the Python Standard Library but you usually want to use MagicMock which is a subclass of Mock. MagicMock provides default implementation for the most of the magic methods (e.g. __setitem__() and __getitem__())

A potential use case could be something like this:

In [4]:
import random


class Client:
    def __init__(self, url, username, password):
        self.url = url
        self.creds = (username, password)

    def fetch_some_data(self):
        print(
            "Here we could for example fetch data from 3rd party API and return the data."
        )
        print("Now we will just return some random number between 1-100.")
        return random.randint(1, 100)


class MyApplication:
    def __init__(self):
        self.client = Client(
            url="https://somewhere/api", username="John Doe", password="secret123?"
        )

    def do_something_fancy(self):
        data = self.client.fetch_some_data()
        return data ** (1 / 2)  # let's return a square root just for example


####################
# In the test module:

from unittest.mock import MagicMock

# Inside a test case:
app = MyApplication()
app.client = MagicMock()  # Mock the client
app.client.fetch_some_data.return_value = 4  # Set controlled behaviour
result = app.do_something_fancy()
assert result == 2
print("All good, woop woop!")
All good, woop woop!

patch¶

The use cases of patch are pretty similar to MacigMock. The biggest difference is that patch is used as a context manager or a decorator. Object to be patched is given as an argument for patch. In addition, you can provide additional object as a second argument (new) which will replace the original one. In case the new is omitted, MagicMock will be used by default.

Let's see how the example above would look like with patch.

In [5]:
# In the test module:

from unittest.mock import patch

# Inside a test case:
app = MyApplication()
with patch("__main__.app.client") as patched_client:  # Patch the client
    patched_client.fetch_some_data.return_value = 4  # Set controlled behaviour
    result = app.do_something_fancy()
    assert result == 2
    print("All good, woop woop!")
All good, woop woop!

The same but with a function decorator instead of a context manager. Note that here we are patching the whole Client class, not just the client instance variable of app.

In [6]:
from unittest.mock import patch


@patch("__main__.Client")  # Patch the Client
def test_do_something_fancy(client_cls):
    client_cls().fetch_some_data.return_value = 4  # Set controlled behaviour
    app = MyApplication()
    result = app.do_something_fancy()
    assert result == 2
    print("All good, woop woop!")


test_do_something_fancy()  # This is just for the sake of example
All good, woop woop!

collections¶

namedtuple¶

A great helper for creating more readable and self documenting code.

namedtuple is a function that returns a tuple whose fields have names and also the tuple itself has a name (just like classes and their instance variables). Potential use cases include storing data which should be immutable. If you can use Python 3.7 or newer, you may want to take a look at dataclasses as well.

In [7]:
from collections import namedtuple

Person = namedtuple("Person", ["name", "age", "is_gangster"])

# instance creation is similar to classes
john = Person("John Doe", 83, True)
lisa = Person("Lis Doe", age=77, is_gangster=False)

print(john, lisa)
print(f"Is John a gangster: {john.is_gangster}")

# tuples are immutable, thus you can't do this
# john.is_gangster = False
Person(name='John Doe', age=83, is_gangster=True) Person(name='Lis Doe', age=77, is_gangster=False)
Is John a gangster: True

Counter¶

For counting the occurences of elements in a collection.

In [8]:
from collections import Counter

data = [1, 2, 3, 1, 2, 4, 5, 6, 2]

counter = Counter(data)
print(f"type: {type(counter)}, counter: {counter}")

print(f"count of twos: {counter[2]}")
print(f"count of tens: {counter[10]}")  # zero for non existing

print(f"counter is a dict: {isinstance(counter, dict)}")
type: <class 'collections.Counter'>, counter: Counter({2: 3, 1: 2, 3: 1, 4: 1, 5: 1, 6: 1})
count of twos: 3
count of tens: 0
counter is a dict: True

defaultdict¶

For cleaner code for populating dictionaries.

Let's first see how you could use a normal dict.

In [9]:
data = (1, 2, 3, 4, 3, 2, 5, 6, 7)

my_dict = {}
for val in data:
    if val % 2:
        if not "odd" in my_dict:
            my_dict["odd"] = []
        my_dict["odd"].append(val)
    else:
        if not "even" in my_dict:
            my_dict["even"] = []
        my_dict["even"].append(val)

print(my_dict)
{'odd': [1, 3, 3, 5, 7], 'even': [2, 4, 2, 6]}

With defaultdict:

In [10]:
from collections import defaultdict

my_dict = defaultdict(list)
for val in data:
    if val % 2:
        my_dict["odd"].append(val)
    else:
        my_dict["even"].append(val)
print(my_dict)
defaultdict(<class 'list'>, {'odd': [1, 3, 3, 5, 7], 'even': [2, 4, 2, 6]})

In the above example, defaultdict makes sure that a fresh list is automatically initialized as a value when a new key is added.

Here's another example with int as a default.

In [11]:
my_dict = defaultdict(int)
for val in data:
    if val % 2:
        my_dict["odd_count"] += 1
    else:
        my_dict["even_count"] += 1
print(my_dict)
defaultdict(<class 'int'>, {'odd_count': 5, 'even_count': 4})