Skip to content

Guide to developers

joaomcteixeira edited this page Dec 13, 2018 · 8 revisions
Updated: December 2018
Farseer-NMR version: +1.3.0

Hello!

The Farseer-NMR team salutes and thanks you for showing interest in contributing to the Farseer-NMR project! You may wish to read the CONTRIBUTING file before continuing.

Farseer-NMR was developed to be as modular as our imagination reaches. The core of the Farseer-NMR consists of a workflow that orchestrates the operations to be performed. At the end of each workflow operation line, a list of available routines is presented and, depending on the active flags from user configuration, those routines are executed.

The idea behind this implementation aims at simplifying the most the implementation of new additions. In this way, if a developer wants to add a new routines to a workflow line, she/he just needs to add the new function call to the that list of routines; and define the new function in the proper library.

Many of our workflow lines comply with such strategy, but not all of them do it yet. As the Farseer-NMR code evolves this documentation page will be updated accordingly to provide the guides for developers in the different parts of Farseer-NMR architecture.

IMPORTANT: Farseer-NMR is evolving fast, expect changes in the code architecture and APIs. Please do not hesitate in contacting us via [email protected].

Index

  1. Dependencies
  2. Farseer-NMR Code Style

Dependencies

Keep updated to the latest

Be not afraid to update your code to the latest versions within the Python community. But keep an eye not to break backwards compatibility. If necessary you can propose an update to the farseernmr.yml Conda ENV.

Choose carefully

Farseer-NMR opts for long-term stability, yet it is completely open for new methodologies. In this regard, rely as much as possible on larger and well stablishes projects for dependencies, and avoid those that are prone to change quickly and drastically in the future. For example:

  • use Python standard libraries whenever possible
  • use numpy instead of pandas whenever possible, even if that requires more lines of code
  • use matplotlib instead of other plotting libraries unless you are seeking for an exclusive feature

IMPORTANT: we are not judging the quality of any library at all. All Python libraries are spectacular :-) and highly important to the community, we simply prime long-term stability.

Farseer-NMR Code Style

The Farseer-NMR Code Style follows PEP8 rules yet allowing some necessary exceptions. The following flake8 options enforce Farseer-NMR coding style: flake8 --hang-closing --ignore=W293,W503,E402 (pease follow these rules when submitting Pull Requests)

# W293 blank line contains whitespaces
# W503 line break before binary operator, actually favoured by PEP8
# E402 module level import not at top of file, in Farseer-NMR sometimes is necessary to import later on
# -hang-closing, allows:
#my_func(
#    var1,
#    var2,
#    )

Bellow a detailed description of the major points:

Length of line

Maximum line length 79 chars. Maximum docstring length 72 chars.

Indentation

Indentations are 4 spaces NOT tabs.

Method call

If method call cannot fit one line, break the line after the "." followed by an extra indentation to the current indentation block. Continue this proceedure for consecutive calls.

data_frame_with_very_long_name.\
    loc["a",:]

Function call

Whenever a function call cannot fit a single line, create and indent block for all arguments, closing the call should be aligned with args:

Yes:

func_with_long_name(
    positional_arg1,
    positional_arg2,
    arg1='foo',
    arg2='bar'
    )

Alignment with opening delimiter it's allowed by PEP8, but annoying and difficult to maintain (in my personal opinion) because it introduces partial indentation blocks. Let's not use it.

No:

func_with_long_name_falls_outside_tab(positional_arg1,
                                      positional_arg2,
                                      arg1='foo',
                                      arg2='bar'
)

Kwargs

Create a line break "\" after the assignment statement of the kwarg if the argument can, in this way, fit a new line. Otherwise proceed as in Function Call and Method Call sections.

Preferable, if series_kwargs() call fit a single line:

func(
    posvar1,
    series_kwargs=\
        series_kwargs(fsuv, resonance_type=resonance_type)
    )

Yes, if series_kwargs() call cannot fit a single line:

func(
    posvar1,
    series_kwargs=series_kwargs(
        fsuv,
        resonance_type=resonance_type
        )
    )

Usage in case of method calls:

tmp_msg = "this long string with several {} {} {}"
tmp_msg = tmp_msg.format(a, b, c)

func(
    posvar1,
    message=tmp_msg
    kwargs=var1
    )

Defining a function

In case all the positional args and kwargs can not fit a single line, break line after "(" and write the args in a new indent block. Give a blank line after the function definition.

def long_function_name(
        var_one,
        var_two,
        var_three,
        var_four,
        kwarg1='default',
        kwarg2=0.0
        ):
    
    print(var_one)

Return statement

Citing PEP8: "Be consistent in return statements. Either all return statements in a function should return an expression, or none of them should."

Conditionals

In if statements newlines should be followed by double indentation to separate from nested code and followed by the necessary subindentation. If long conditionals have to be created feel free to create variables to assign temporary short alias. Allows flake8 --ignore=W503.

if (True and (True or False)) \
        and (True \
            or False) \
        and False:
    
    # do something

List, Dictionaries and alike

Use the following. This applies also in function calls.

my_list = [
    value1,
    value2,
    [
        value3,
        value4,
        value5
        ],
    ]
my_dict = {
    "var1": 1,
    "var2: 2
    }

For loops

For example, if long zip() is needed:

Yes:

for sourcecol, targetcol in zip(
        fsuv["restraint_settings"].index[3:],
        ['Hgt_DPRE', 'Vol_DPRE']
        ):

Nested loops

Use itertool library whenever possible.

Yes:

for dp2, dp1 in it.product(next_axis_2, next_axis):
    # DO

No:

for dp2 in next_axis_2:
    for dp1 in next_axis:
        # DO

Break in binary operators.

Break before as in PEP8. Allows flake8 --ignore=W503.

Strings

Single line

When assigning long strings:

msg = (
    "this big string message"
    "with that cannot fit in"
    "single line and that has"
    "lots of formatters {} {}"
    "and some {} {} {}"
    )

using multi-line strings:

    def dummy_func():
        multiline_log_message = """
**[{}][{}][{}]** new columns inserted:  {}
| sidechains user setting: {}
| sidechains identified: {} | SD count: {}
"""
        msg = multiline_log_message.format(
            a,
            b,
            c,
            d
            )

Imports

Follow the rules of PEP8.
In Farseer-NMR sometimes it is necessary to perform imports away from the first lines. allows flake8 --ignore=E402.

Blank lines and Whitespaces

Follow the general rules of PEP8. flake8 --ignore=W293 is allowed.

Inside the same indentation block, write the operations consecutively. Optionally you can separate relevant logical blocks by new lines or, preferable, by comments.

Separating indentation blocks

Separate a new indentation block with a preceeding and succeeding blank line. Start the block right after the header. If a block initiates right after another avoid the preceeding blank line.

a = 1
b = 2

if a > 5:
    #do something

c = 4

or

for char in string:
    for digit in dct[char]:
        # Do something here
        # in the second for loop
    
    # continue with the first for loop

page in pareparation