Coding Standards#
Aviary uses a combination of formal standards and general best practices. To contribute code to the Aviary codebase, we request you follow these guidelines.
In general, always follow the excellent advice given in the PEP 8 Python style guide. Consistency is also key - pick a convention and stick with it for an entire file.
Style and Formatting#
The Aviary development team uses the ruff formatter to handle formatting in a consistent way across the codebase. Ruff is a tool that formats Python code (through alteration of whitespace and line breaks) to follow a consistent style and attempt to keep lines within the character limit whenever possible. Aviary uses ruff as part of its pre-commit scripts. Aviary includes a configuration script for ruff, so devs can directly run ruff within the repository and it will use the correct rules. Aviary uses a maximum line length of 100 as a compromise between short, readable lines, and preventing exessive line breaks. The linting capability of ruff is also recommended to be run on all code you plan to contribute to Aviary, but it is currently not required.
Ruff is not a dependency for Aviary, and is optional to install - if you are using pre-commit, that package will download and install a special version of ruff that is used only for pre-commit hooks. Installing ruff directly in your python environment is only needed if you wish to manually run the formatter or linter on your code before commiting.
Pre-Commit Setup#
To set up pre-commit in your development python environment, there are a few one-time steps that must be done. The following commands need to be run to install pre-commit.
pip install pre-commit
pre-commit install
The Aviary repository contains a configuration file that defines what is run when commits are made and with what options enabled. Currently this is limited to formatting with ruff.
Controlling Display Levels#
To make debugging issues easier, it is strongly recommended to make use of the Verbosity
enum (located in aviary/variable_info/enums.py). This allows control over how much information is displayed to a user; too much information makes finding relevant information difficult and not enough information can make tracking difficult. Aviary uses a sliding scale of possible verbosity settings, loosly based off of Ubuntu’s format:
Verbosity Level |
Numerical Value |
Description |
---|---|---|
|
0 |
All output except errors are suppressed |
|
1 |
Only important information is output, in human-readable format |
|
2 |
All user-relevant information is output, in human-readable format |
|
3 |
Any information can be outputted, including warnings, intermediate calculations, etc., with no formatting requirement |
Verbosity levels are defined in Aviary using the Verbosity
Enum. Each verbosity level is paired with an integer value. In source code, verbosity level can be checked either through comparison with the Enum, or through equality or inequality comparisons with the matching integer value. This allows for code to be triggered not just at a specific level, but for any level above or below the desired setting. Numerical comparisons are recommended for several reasons: they don’t require importing the Verbosity
Enum, and activation is more flexible through the use of inequality comparators, preventing issues like a message only being outputted during BRIEF
but not VERBOSE
or DEBUG
, which a user would expect to also see in higher verbosity settings.
BRIEF
is the default setting and is used in most cases; however, QUIET
should be used for tests.
It is preferred that within source code, the full Enums are used for better readability (e.g. Verbosity.BRIEF
). For tests, scripts, examples, and other places where Aviary is called (rather than defined), it is ok to use the integer representations of verbosity to shorten lines and remove the need to import the Verbosity
Enum (e.g. passing 0
as the verbosity argument to a function when QUIET
is desired). An example of this is: options.set_val(Settings.VERBOSITY, 0) instead of options.set_val(Settings.VERBOSITY, Verbosity.QUIET). Of course, it is always acceptable to use the full Enum in these cases for the same readability reasons.
Naming Conventions#
Variables#
When it comes to variable naming, always be verbose! The Aviary team considers long but clear and descriptive names superior to shortened or vague names. Typing out a long name is only difficult once, as most IDEs will help you auto-complete long variable names, but the readability they add lasts a lifetime! The Aviary variable hierarchy is an excellent example of variable naming in Aviary. When adding variables to the hierarchy, adhering to the following naming conventions is requested. Inside the codebase itself, such as inside openMDAO components, it is not required but still highly recommended to follow these guidelines.
A good variable name should:
Not be ambiguous (avoid names that cannot be understood without context, like x or calc)
Avoid abbreviation (thrust_to_weight_ratio preferred to T_W_ratio). Note that Aviary will sometimes still shorten extremely long words such as “miscellaneous” to “misc” - use your best judgement!
Use physical descriptions rather than jargon or mathematical symbols (density preferred to rho - even better, include what flight condition this density is at, such as current, sea_level, etc.)
Place adjectives or modifiers after the “main” variable name rather than before (such as thrust_max, thrust_sea_level_static). This makes it is easier to autocomplete using an IDE - simply typing “thrust” will provide you with a handy list of all of the different kinds of thrust you can use.
Be formatted in “snake case”, or all lowercase with underscore-delineated words (such as example_variable)
Classes#
Class names should be written in “camel case”, or naming with no delimiters such as dashes or underscores between words and each word beginning with a capital letter (such as def ExampleClass()).
Functions and Methods#
Function and method names, similar to variables, should be formatted in “snake case”. Class methods that are not intended to be accessed outside of the class definition can append an underscore at the beginning of the method name to mark it as “private”, to help other developers avoid using those methods incorrectly. An example of this is: def _private_method(self):
Import statements#
Ruff’s linting rules allow both absolute and relative paths in import
statements. Aviary will use absolute path option only. Following PEP8, imports should be grouped in the following order:
1. Standard library imports (e.g. warnings, numpy).
2. Related third party imports (e.g. openmdao.api).
3. Local application/library specific imports (e.g. aviary.api).
The library names should be in alphabetic order in each group and there should be a blank line between each group of imports.
Code Re-Use and Utility Functions#
If an identical block of code appears multiple times inside a file, consider moving it to a function to make your code cleaner. Repeated code bloats files and makes them less readable. If that function ends up being useful outside that individual file, move it to a “utils.py” file in the lowest-level directory shared by all files that need that function. If the utility function is useful across all of Aviary and is integral to the tool’s operation, the aviary/utils folder is the appropriate place for it.