Removed, Added Modules

Errors are produced when modules are added to or removed from the namespace of a module within the public API.

The external-module errors are used when the added/removed module is outside of the public API root.

Error codes

Code

Name

B110

removed-module

B111

removed-external-module

N210

added-module

N211

added-external-module

Example: adding internal module

Initial version of code:

# in kitchen/__init__.py
def fry(eggs):
    return FryingPan().fry(eggs)

Updated version of code:

from kitchen.containers import cabinet

def fry(eggs):
    return cabinet.find(FryingPan).fry(eggs)

As an (internal) module has been added to the namespace, an error will be produced:

kitchen/__init__.py:0: N210 module added: cabinet

This constitutes a minor API change since new code could be written including from kitchen import cabinet, which would not work with the older version of the code.

Example: removing external module

Initial version of code uses re module for regular expressions:

# in haystack/__init__.py
import re

class Haystack:
    def search(self, needle):
        return re.search(needle, self._content)

Updated version of code migrated to regex module:

import regex

class Haystack:
    def search(self, needle):
        return regex.search(needle, self._content)

This change will produce an error for the major API change of removing a formerly available module (as well as the less severe change of adding a new module). Since re lives outside of the haystack namespace, the module is referred to as “external”.

haystack/__init__.py:0: B111 external module removed: re
haystack/__init__.py:0: N211 external module added: regex

Discussion

Adding a module is, of course, often expected when adding new functionality. However, it can also happen accidentally as a result of changing implementation.

The first example above shows how this can result in the same module becoming available under multiple paths. In that example, with the new version of the code, a client is able to write either of the following:

  • from kitchen import cabinet

  • from kitchen.containers import cabinet

Both appear valid, and there’s no hint to the client that cabinet is only available from kitchen due to implementation details. If backwards compatibility is valued, both imports must continue to work even as the implementation is further revised, adding to the maintenance burden.

Adding or removing external modules can also cause compatibility issues. Consider the second example above. It seems unlikely that a client would intentionally write code such as from haystack import re to access the re module. On the other hand, it’s quite easy for clients to accidentally write code accessing external modules through other modules without realizing it, as in example:

# 're' imported into this file's namespace here
from haystack import *

# ...later, some code using re
re.search(pattern, value)