Designing a package with alternate implementations

In some cases, we'll have a top-level __init__.py file that chooses between some alternative implementations within the package directory. The decision might be based on the platform, CPU architecture, or the availability of OS libraries.

There are two common design patterns and one less common design pattern for packages with alternative implementations:

  • Examine platform or sys to determine the details of the implementation and decide what to import with an if statement.
  • Attempt import and use a try block exception handling to work out the configuration details.
  • As a less common alternative, an application may examine a configuration parameter to determine what should be imported. This is a bit more complex. We have an ordering issue between importing an application configuration and importing other application modules based on the configuration. It's far simpler to import without this potentially complex sequence of steps.

We'll show the structure of a hypothetical package named some_algorithm. This will be the name of the top-level directory. To create the complex package, the some_algorithm directory must include a number of files, described as follows:

  • An __init__.py module will decide which of the two implementations to import. This name is required to define a package. The contents of this module will be shown in the following code block.
  • An abstraction.py can provide any necessary abstract definitions for the two implementations. Using a single, common module can help to provided consistent type hints for mypy checking.
  • Each implementation will be another module in the package. We'll outline two implementation choices, called short_module.py and long_module.py. Neither of these module names will be visible outside the package.

Here's __init__.py for a some_algorithm package. This module chooses an implementation based on the platform information. This might look like the following example:

import sys
from typing import Type

from Chapter_19.some_algorithm.abstraction import AbstractSomeAlgorithm

SomeAlgorithm: Type[AbstractSomeAlgorithm]

if sys.platform.endswith("32"):
from Chapter_19.some_algorithm.short_version import *
SomeAlgorithm = Implementation_Short
else:
from Chapter_19.some_algorithm.long_version import *
SomeAlgorithm = Implementation_Long

This module defines the SomeAlgorithm class based on one of two available implementation modules. For 32-bit platforms, the short_version.py module provides a class named Implementation_Short that will be used. For 64-bit platforms, the long_version.py module provides the Implementation_Long class.

We need to also provide two modules within the some_algorithm package; the long_version.py module provides an implementation appropriate for a 64-bit architecture; the short_version module provides an alternate implementation. The design must have module isomorphism; this is similar to class isomorphism. Both the modules must contain classes and functions with the same names and same APIs.

If both the files define a class named SomeClass, then we can write the following code in an application:

from Chapter_19 import some_algorithm
x = some_algorithm.SomeAlgorithm() 

We can import the some_algorithm package as if it were a module. This will import the some_algorithm/__init__.py module. This module locates an appropriate implementation and provides the needed class definition.

Each implementation is similar. Both will incorporate the abstract class to make it clear to tools such as mypy that the two implementations are identical. Here is the content of the short_implementation.py module.

from .abstraction import AbstractSomeAlgorithm

class Implementation_Short(AbstractSomeAlgorithm):

def value(self) -> int:
return 42

This module imports an abstract class definition. It then defines a proper subclass. This overhead helps mypy confirm the defined class is a complete implementation of the abstract class definition.

For complex applications, this kind of alternative implementation strategy can be very helpful. It lets a single code base work in a number of environments where configuration changes are made as late in the deployment pipelines as possible.

The next section shows how to use the ImportError exception.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset