2. Inside the party

This chapter describes how to write custom module-extensions for teapot.

2.1. Filters

We already seen in Filters what a filter is. Now is the time to write your owns !

A filter is a simple function that takes no parameters and returns a boolean value.

To register a new filter, use the teapot.filters.decorators.named_filter() decorator.

class teapot.filters.decorators.named_filter(name, depends=None, override=False)[source]

Registers a function to be a filter.

__init__(name, depends=None, override=False)[source]

Registers the function with the specified name.

If another function was registered with the same name, a DuplicateFilterError will be raised, unless override is truthy.

depends can be either:
  • None, if the filter does not depend on any other filter.
  • A str instance, being the registered name of another filter to depend on.
  • A list of str instances, being the list of registered names of other filters to depend on.

If a filter depends on other filters, those will be checked before the actual filter gets run.

Here is an example of some built-in filters:

import os
import sys

from teapot.filters.decorators import named_filter

@named_filter('windows')
def windows():
    """
    Check if the platform is windows.
    """

    return sys.platform.startswith('win32')

@named_filter('msvc', depends='windows')
def msvc():
    """
    Check if MSVC is available.
    """

    return 'VCINSTALLDIR' in os.environ

2.2. Extensions

As seen in Extensions, an extension is a function, that always takes a builder argument, optionally takes string parameters and returns a string.

Extensions are to teapot what macros are to the C language.

To register a new extension, use the teapot.extensions.decorators.named_extension() decorator.

Here is an example of some built-in extensions:

import os
import sys

from teapot.path import windows_to_unix_path
from teapot.extensions.decorators import named_extension

@named_extension('prefix')
def prefix(builder, style='default'):
    """
    Get the builder prefix.
    """

    result = os.path.join(builder.attendee.party.prefix, builder.attendee.prefix, builder.prefix)

    if sys.platform.startswith('win32') and style == 'unix':
        result = windows_to_unix_path(result)

    return result

Note that the function must always have first builder argument that will be valued with the current teapot.builders.Builder instance.

2.3. Fetchers

Fetchers are responsible for downloading or copying the source archives from a specified location.

To define a new fetcher, just derive from teapot.fetchers.base_fetcher.BaseFetcher.

Here is an example with the built-in file fetcher:

import os
import shutil
import mimetypes

from teapot.fetchers.base_fetcher import BaseFetcher


class FileFetcher(BaseFetcher):

    """
    Fetchs a file on the local filesystem.
    """

    shortname = 'file'

    def read_source(self, source):
        """
        Checks that the `source` is a local filename.
        """

        if os.path.isfile(source.location):
            self.file_path = os.path.abspath(source.location)

            return True

    def do_fetch(self, target):
        """
        Fetch a filename.
        """

        archive_path = os.path.join(target, os.path.basename(self.file_path))

        archive_type = mimetypes.guess_type(self.file_path)
        size = os.path.getsize(self.file_path)

        self.progress.on_start(target=os.path.basename(archive_path), size=size)

        shutil.copyfile(self.file_path, archive_path)

        # No real interactive progress to show here.
        #
        # This could be fixed though.

        self.progress.on_update(progress=size)
        self.progress.on_finish()

        return {
            'archive_path': archive_path,
            'archive_type': archive_type,
        }

2.3.1. Callbacks

All callback classes derive from teapot.fetchers.callbacks.BaseFetcherCallback.

2.4. Unpackers

Unpackers are responsible for extracting the content of the source archives into an exploitable source tree.

To define a new unpacker, just derive from teapot.unpackers.base_unpacker.BaseUnpacker.

class teapot.unpackers.base_unpacker.BaseUnpacker(attendee)[source]

Base class for all unpacker classes.

If you subclass this class, you will have to re-implement the do_unpack() method to provide your specific unpacker logic.

If you desire to attach your unpacker to certain mimetypes, please do so by defining the class-level member types that must be a list of couples (mimetype, encoding).

do_unpack()[source]

Unpack an archive.

The archive to unpack can be reached at self.archive_path.

This method must return a dict with the following keys:
  • source_tree_path: The extracted source tree path.

It must raise an exception on error.

You can provide feedback on the unpacking operation by calling self.progress.on_start, self.progress.on_update and self.progress.on_finish at the appropriate time.

See teapot.unpackers.callbacks.BaseUnpackerCallback for further details.

Here is an example with the built-in tarball unpacker:

from teapot.unpackers.base_unpacker import BaseUnpacker

import os
import tarfile


class TarballUnpacker(BaseUnpacker):

    """
    An unpacker class that deals with .tgz files.
    """

    types = [
        ('application/x-gzip', None),
        ('application/x-bzip2', None),
    ]

    def do_unpack(self):
        """
        Uncompress the archive.

        Return the path of the extracted folder.
        """

        if not tarfile.is_tarfile(self.archive_path):
            raise InvalidTarballError(archive_path=self.archive_path)

        tar = tarfile.open(self.archive_path, 'r')

        # We get the common prefix for all archive members.
        prefix = os.path.commonprefix(tar.getnames())

        # An archive member with the prefix as a name should exist in the archive.
        while True:
            try:
                prefix_member = tar.getmember(prefix)

                if prefix_member.isdir:
                    break

            except KeyError:
                pass

            new_prefix = os.path.dirname(prefix)

            if prefix == new_prefix:
                raise TarballHasNoCommonPrefixError(archive_path=self.archive_path)
            else:
                prefix = new_prefix

        source_tree_path = os.path.join(self.attendee.build_path, prefix_member.name)

        self.progress.on_start(count=len(tar.getmembers()))

        for index, member in enumerate(tar.getmembers()):
            if os.path.isabs(member.name):
                raise ValueError('Refusing to extract archive that contains absolute filenames.')

            self.progress.on_update(current_file=member.name, progress=index)
            tar.extract(member, path=self.attendee.build_path)

        self.progress.on_finish()

        return {
            'source_tree_path': source_tree_path,
        }

2.4.1. Callbacks

All callback classes derive from teapot.unpackers.callbacks.BaseUnpackerCallback.

2.5. Party post-actions

post-actions are simple function that takes a teapot.party.Party instance as a parameter, and that gets executed right after a such instance was initialized.

You can register a post-action using the teapot.party.Party.register_post_action() decorator.

Here is an example a built-in post-action that registers the defaut environment:

@Party.register_post_action
def add_default_environment(party):
    """
    Add the default environment to the party.
    """

    party.environment_register.register_environment(DEFAULT_ENVIRONMENT_NAME, create_default_environment())
Read the Docs v: 1.2
Versions
latest
1.2
1.1
1.0
Downloads
On Read the Docs
Project Home
Builds

Free document hosting provided by Read the Docs.