Simply patch what is unextendable.

An issue, that i can't extend a method __str__, was encountered when i retrieved a list of objects from a third-party service and printed them to see the details, but i got uninformative output. Due to that, i dove into source code to figure out what can i get and came to a point that only tittle, text attributes are printing of those objects and the method can't be extended, as a client library doesn't provide customization and it shouldn't indeed.

A rough example of objects retrieving.

from client_library import fetch_entities


def fecth_print():
    entities = fetch_entities()
    print(entities)  #  Print to see the details and examine the entities.
    
# Output is minimalistic:
# "Shop, Buy food"
# "Gym, Do squats"

Base class with __str__ method the entities are of.

class Note(TopLevelNode):
    """Represents a Google Keep note."""
    _TYPE = NodeType.Note
    def __init__(self, **kwargs):
		...

    def _get_text_node(self):
        ...

    @property
    def text(self):
        ...

    @text.setter
    def text(self, value):
        ...
        
    def __str__(self):
        return '\n'.join([self.title, self.text])

Solution

Restriction is clear and what can be done to see more details and which i specifically need. An idea came into my mind, why not to reuse patching technique of a testing practice, unittest.mock is powerful for such a case.

Things to do are simply define a function of a custom printing objects details and patch the __str__ method. So easy and tricky, isn't it?

Here we go.

from unittest.mock import patch
from contextlib import AbstractContextManager


def node_to_str(self: Note):
    created = self.timestamps.created.isoformat(sep=' ', timespec='seconds')
    updated = self.timestamps.updated.isoformat(sep=' ', timespec='seconds')
    return f'Node<{self.type.value}, "{self.title}", "{self.text}", created="{created}", updated="{updated}">'


class Patch(AbstractContextManager):

    def __enter__(self):
        self.patchers = []
        node_str_mock = patch('gkeepapi.node.Note.__str__', node_to_str)
        self.patchers.append(node_str_mock)

        for p in self.patchers:
            p.start()

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        for p in self.patchers:
            p.stop()

Function node_to_str is the way i'd like to see the object's details and the Patch context-manager class is a wrapper to apply it gracefully.

At a final step running code looks very tidy.

from fetchig import fecth_print


if __name__ == '__main__':
    from patching import Patch

    with Patch():
        fecth_print()
        
# Output looks better now:
# "Node<1, Shop, Buy food, created=2022-01-24 17:53:56Z, updated=2022-01-24 17:53:56Z>"
# "Node<1, Gym, Do squats, created=2022-01-24 17:53:56Z, updated=2022-01-24 17:53:56Z>"

It's a bad approach

Goal is reached, code isn't production consequently it's a quite good approach.

Table of content
  1. Solution
  2. It's a bad approach