Search code examples
pythonpython-2.7sonarlint

Python Refactor this function to reduce its Cognitive Complexity from 19 to the 15 allowed


I see this message from sonarlint and trying to figure out how to reduce the Cognitive Complexity of this function. Any assistance is appreciated in advance.

import os
import json
import click
import hcl

cfn = [".json", ".template", ".yaml", ".yml"]
tf  = ["tf"]

def file_handler(dir):
    for root, dirs, files in os.walk(dir):
        for file in files:
            if file.endswith(tuple(cfn)):
                with open(os.path.join(root, file), 'r') as fin:
                    try:
                        file = fin.read()
                        if "AWSTemplateFormatVersion" in file:
                            data = json.dumps(file)
                            print(data)

                    except ValueError as e:
                        raise SystemExit(e)

            elif file.endswith(tuple(tf)):
                with open(os.path.join(root, file), 'r') as file:
                    try:
                        obj  = hcl.load(file)
                        data = json.dumps(obj)
                        print(data)
                    except ValueError as e:
                        raise SystemExit(e)
    return data

Solution

  • Extract function to yield file paths:

    def _file_paths(directory):
        for root, dirs, filenames in os.walk(directory):
            for filename in filenames:
                file_path = os.path.join(root, filename)
                if os.path.isfile(file_path):
                    yield file_path
    

    Unify exception handling:

    from contextlib import contextmanager
    
    @contextmanager
    def _translate_error(from_error, to_error):
        try:
            yield
        except from_error as error:
            raise to_error(error)
    

    Extract functions to handle file types:

    def _handle_cfn(file_object):
        file_contents = file_object.read()
        if "AWSTemplateFormatVersion" in file_contents:
            data = json.dumps(file_contents)
            print(data)
    
    
    def _handle_tf(file_object):
        obj  = hcl.load(file_oject)
        data = json.dumps(obj)
        print(data)
    

    Create a null handler for when the file extension doesn't match:

    def _null_handler(file_object):
        pass
    

    Map file extensions to handlers:

    _extension_handlers = {'.json': _handle_cfn,
                '.template': _handle_cfn,
                '.yaml': _handle_cfn,
                '.yml': _handle_cfn,
                '.tf': _handle_tf}
    

    Putting it together:

    import os
    import json
    import click
    import hcl
    
    def file_handler(dir):
        for file_path in _file_paths(dir):
            base, extension = os.path.splitext(file_path)
            handler = _extension_handlers.get(extension, _null_handler)
            with open(file_path) as file_object:
                with _translate_error(ValueError, SystemExit):
                    handler(file_object)
    

    You can go further by extracting out a function for handling each file:

    def _handle_file(file_path):
        base, extension = os.path.splitext(file_path)
        handler = _extension_handlers.get(extension, _null_handler)
        with open(file_path) as file_object:
            with _translate_error(ValueError, SystemExit):
                handler(file_object)
    

    Then your main function is:

    def file_handler(dir):
        for file_path in _file_paths(dir):
            _handle_file(file_path)