Coding Style Guide

Note

This style guide is a work in progress. As such, sections may have more detail or change in the future. It is the developer’s responsibility to remain aware of convention changes and to maintain their code appropriately.

This is the coding style guide for the Mari Tools. It contains valuable information on how developers should and should not format their code when extending functionality.

Most of this will boil down to the following 2 rules:

  • Whenever possible, follow PEP-8 coding standards when scripting in Python.
  • Make sure the documentation builds without warnings.

Other than that, the rest of this guide will formalize the standards that are expected of developers who work with this project.

Python is the main scripting language used in this toolkit. As such, this style guide will cover the do’s and don’ts for working primarily with it.

Python

Imports

  • Use import statements for packages and modules only. from statements should be used to import only the necessary objects required in all other instances.
  • Do not use relative imports. (i.e. from ..package.module import function) Among other things, this helps to avoid conflicts in module names during imports .
  • For that matter, do not use duplicate module names, even for submodules! This will help to eliminate problems with clashing module names in the sys.modules namespace.

Flow Control

  • Exceptions are allowed but must be used carefully, so as not to make the control flow of code confusing.
  • Catch-all exceptions should only be used as a matter of last resort, or when attempting to ensure that exceptions will always print traceback results for the user to send debug logs for.
  • Protect script routines from being executed from a mere import statement. This is necessary for unit tests and for Sphinx to be able to generate documentation without issues. Your code should always check if __name__ == '__main__' before executing your main entry point so that the main program is not executed when the module is imported.
    • Nested/local/inner classes and functions are fine.

Objects

  • Avoid global variables, if at all. There is almost never a single case where they are preferred over module-level constants.
  • Avoid overly-complex lambda functions and list comprehensions. For raw performance, consider switching your tool to use a compiled-code option over Python instead.
  • Using default argument values is allowed, provided that they do not use mutable objects, since they are evaluated at module load time and could cause potential problems. Additionally, be wary of using objects that are not part of the standard library as default argument values, as evaluating such values could cause runtime exceptions to occur should the application environment not be setup correctly.

Formatting

  • Maximum line length should be kept to around 80 characters. Around in this case means something reasonable. i.e. Do not use line-breaks that would hurt the readability of the code.

  • Indent your code using spaces only, and 4 spaces to an indent.

  • Use two blank lines between top-level definitions and one blank line between method definitions.

  • Use todo comments for code that is temporary, a short-term solution, or good-enough but not perfect.

  • Use the following conventions when naming your objects:

    module_name, package_name, ClassName, method_name, ExceptionName,
    function_name, GLOBAL_CONSTANT_NAME, global_var_name, instance_var_name,
    function_parameter_name, local_var_name.
    

    CamelCase is acceptable, but should be deprecated in favour of said convention whenever possible.

  • The standard shebang line should be present at the header of every file that you create for these libraries. In addition to this, every module should have a docstring giving a brief overview of its purpose, like so:

    #!/usr/bin/env python
    # -*- coding: UTF-8 -*-
    
    """ Module constants: This module contains global variables that are used by ftrack. """
    

    Failure to include either of these two items raises the risk of incorrect compilation by the Python interpreter, along with increasing confusion among developers.

  • Document your code. ReStructuredText -style comments and docstrings are the standard used throughout this toolkit.

    Every method, class and function should have an appropriate docstring accompanying it, with the following items:

    • A brief overview of what the object is/does.
    • A listing of all input parameters that it accepts.
    • A listing of all possible return values and their significance.

    An example of a function definition, with its accompanying docstring, in ReST format, is as follows:

    def read(path, decryptKeys=False, decryptValues=False, decryptionKey=None):
    """ Given a JSON-formatted file, reads the data from it.
    Returns the equivalent Python object.
    
    :param decryptionKey: ``str`` of decryption key as a 16-byte string.
    
    :param decryptKeys: ``bool`` determining if keys read should be decrypted.
    
    :param decryptValues: ``bool`` determining if values read should be decrypted.
    
    :param path: ``str`` that determines where open() will attempt to read data from
    
    :return: Depending on type of JSON object stored, returns Python equivalent.
        Defaults to ``dict``.
    
    :rtype: ``dict``
    """
    

    Several IDEs are capable of automatically generating these docstrings, including PyCharm and Wing. Failure to include these ultimately makes it more difficult for code inspection to take place and weakens the usefulness of the API reference, ultimately leading to more confusion among developers.

Note

While NumPy and Google-style comments are supported through the use of sphinx-napoleon, it is advised that you stick to the standardized ReST format for docstring syntax, as it is the most consistent, the most reliable, and can be automatically inspected and generated by most Python IDEs, including PyCharm and Wing. Using other docstring formats can lead to potential confusion among authors and ultimately leads to frustration.

  • It is highly recommended to run pylint over your code in order to catch common problems and warnings. However, pylint isn’t perfect, and often will require suppression of warnings through use of code comments. Take care not to disable valid warnings!

Agnosticity

  • As far as possible, write your code to support both Python 2.x and Python 3.x. This means that even simple statements such as print should be used as a function instead, rather than as a statement. (i.e. print('Hello') as opposed to print 'Hello')

    More examples of this include the use of imp versus importlib and __import__. You should use your discretion as to which method works best, but always ensure that you are thinking ahead, even if the rest of the DCC world does not seem to follow that mentality.

Debugging

  • While on this topic, use logging levels to handle debug output as far as possible, and refrain from using print functions to handle debugging.

Power Features

  • Avoid using fancy features such as metaclasses, access to bytecode, on-the-fly compilation, dynamic inheritance, object reparenting, import hacks, reflection, modification of system internals, etc. unless there is a very clear use case or a urgent need for such features to be used.

    Favour clarity over performance at all times.