Search code examples
pythonimportmaintainability

Good practice with conditional imports


I have a module for configuration, projectConfig, to parse a sample sheet for a project:

class SampleSheetFields():
    FIELD_1 = "field1"
    FIELD_2 = "field2"


class SampleSheetFieldsOld():
    FIELD_1 = "field_1"
    FIELD_2 = "field_2"

I had been using the first class in other modules like this:

from projectConfig import SampleSheetFields as ssFields

class SomeClass

    def __init__(self):
        ...
        check(someContent, ssFields.FIELD_1)

The thing is I had developed my software using the reference to ssFields quite a lot. At some point new specifications said that the software should also use sample sheets with different field names. The quickest way I found to achieve that, without messing too much with the code, was to add the class SampleSheetFieldsOld in the projectConfig and to make a conditional import in my modules:

class SomeClass:

    def __init__(self, useOld):
        if useOld:
            from projectConfig import SampleSheetFieldsOld as ssFields
        else:
            from projectConfig import SampleSheetFields as ssFields

        ...
        check(someContent, ssFields.FIELD_1)

Note that the mandatory fields which are used have the same name so there is no conflicting or missing field. The program works as expected.

My questions are:

  1. How bad is this practice, if it is bad; and
  2. How can I circumvent it to make a better and more sustainable code?

Solution

  • It's probably not the worst thing, but what I do find kind of problematic is the fact that you're now locked into two configuration options, old and new. What if you need to add a third or fourth etc. set someday? You won't be able to use a simple boolean test anymore.

    Besides, it looks like your configuration options are all just simple string values, accessible via string keys. You don't need a class for that.

    My suggestion is to forget about doing your configuration with the source code and use a configuration file. In your projectConfig you can have a dict which you initialize from a file, whose path/name can be provided on the command line or in whatever way is convenient. So projectConfig.py might go something like this:

    config_options = {}
    
    def load_configuration(filename):
        with open(filename) as f:
            for line in f:
                # get key and value
                config_options[key] = value
    

    Then everywhere you need to get a field name, just access projectConfig.config_options['field_key'], e.g.

    from projectConfig import config_options
    
    class SomeClass
    
        def __init__(self):
            ...
            check(someContent, config_options['FIELD_1'])
    

    Or use dict.get(key, default) if there is a reasonable default value. This way, each time you need to switch to a different set of field names, you just create a new configuration file, and you don't have to touch the code.

    Python's standard library includes a configparser module which can handle the loading for you.