Code Review: Khal

Documentation

High-level analysis

This analysis is based on setup.py contents and on a first look at the code.

Concepts

Dependencies

Runtime dependencies:

Testing dependencies:

Extra dependencies:

Package organization

Entry points

Two console scripts:

  1. khal = khal.cli:main_khal, which provides a command-result terminal experience.
  2. ikhal = khal.cli:main_ikhal, which provides a TUI (terminal-user interface) handled by code in khal.ui.

Configuration and data files

Configuration

Calendars (data)

Architecture

The following diagram tries to represent the components and relations of the khal (cli) application. This is not complete by any means, many pieces are missing and some might be wrong.

Object-oriented design

The khalendar package contains code which is more object-oriented than in other khal's modules. Let's analyze its classes:

The CalendarCollection class can be seen as this package interface. It uses composition to make use of the SQLiteDb class implementation and also is composed of one Vdir object per configured calendar. It also allows CRUD of Events saved in the SQLite cache and in vdirs using multiple criteria.

Events are represented as Event class instances, which is a base class for different derived classes:

The vdir module makes use of many OO design concepts:

Besides the khalendar package, the other interesting package is ui, which contains multiple classes following urwid's object oriented development design.

Python techniques

    @contextlib.contextmanager
    def at_once(self):
        assert not self._at_once
        self._at_once = True
        try:
            yield self
        except:  # noqa
            raise
        else:
            self.conn.commit()
        finally:
            self._at_once = False
    with self._backend.at_once():
        event.etag = self._storages[event.calendar].update(event.href, event, event.etag)
        self._backend.update(event.raw, event.href, event.etag, calendar=event.calendar)
        self._backend.set_ctag(self._local_ctag(event.calendar), calendar=event.calendar)
# Implements the descriptor protocol making this object a descriptor,
# specifically a non-data descriptor because it implements only the
# __get__ method.
class cached_property:
    '''A read-only @property that is only evaluated once. Only usable
    on class instances' methods.
    '''
    def __init__(self, fget, doc=None):
        self.__name__ = fget.__name__
        self.__module__ = fget.__module__
        self.__doc__ = doc or fget.__doc__
        self.fget = fget

    # This method is called once because an attribute is added to the
    # object via the self.__dict__ object and, in the case of non-data
    # descriptors, the instance's dictionary entry takes precedence.
    def __get__(self, obj, cls):
        if obj is None:  # pragma: no cover
            return self
        obj.__dict__[self.__name__] = result = self.fget(obj)
        return result
class A:
    # When this method is processed by the interpreter, an instance
    # of the class cached_property is created with this function as
    # argument 'fget'.
    @cached_property
    def prop_name(self):
        return 1
>>> a = A()
>>> vars(a)
{}
>>> print(a.prop_name)
1
>>> vars(a)
{'prop_name': 1}

There is much more in khal and I recommend you to take a look at the code and find out by yourself ;-)