Style Guide¶
This document outlines the code style, project structure and practices followed by the Vyper development team.
Note
Portions of the current codebase do not adhere to this style guide. We are in the process of a large-scale refactor and this guide is intended to outline the structure and best practices during and beyond this refactor. Refactored code and added functionality must adhere to this guide. Bugfixes and modifications to existing functionality may adopt the same style as the related code.
Project Organization¶
- Each subdirectory within Vyper should be a self-contained package representing a single pass of the compiler or other logical component.
- Functionality intended to be called from modules outside of a package must be exposed within the base
__init__.py
. All other functionality is for internal use only.- It should be possible to remove any package and replace it with another that exposes the same API, without breaking functionality in other packages.
Code Style¶
All code must conform to the PEP 8 style guide with the following exceptions:
- Maximum line length of 100
We handle code formatting with black with the line-length option set to 80. This ensures a consistent style across the project and saves time by not having to be opinionated.
Naming Conventions¶
Names must adhere to PEP 8 naming conventions:
- Modules have short, all-lowercase names. Underscores can be used in the module name if it improves readability.
- Class names use the CapWords convention.
- Exceptions follow the same conventions as other classes.
- Function names are lowercase, with words separated by underscores when it improves readability.
- Method names and instance variables follow the same conventions as functions.
- Constants use all capital letters with underscores separating words.
Leading Underscores¶
A single leading underscore marks an object as private.
- Classes and functions with one leading underscore are only used in the module where they are declared. They must not be imported.
- Class attributes and methods with one leading underscore must only be accessed by methods within the same class.
Booleans¶
- Boolean values should be prefixed with
is_
.- Booleans must not represent negative properties, (e.g.
is_not_set
). This can result in double-negative evaluations which are not intuitive for readers.- Methods that return a single boolean should use the
@property
decorator.
Methods¶
The following conventions should be used when naming functions or methods. Consistent naming provides logical consistency throughout the codebase and makes it easier for future readers to understand what a method does (and does not) do.
get_
: For simple data retrieval without any side effects.fetch_
: For retreivals that may have some sort of side effect.build_
: For creation of a new object that is derived from some other data.set_
: For adding a new value or modifying an existing one within an object.add_
: For adding a new attribute or other value to an object. Raises an exception if the value already exists.replace_
: For mutating an object. Should returnNone
on success or raise an exception if something is wrong.compare_
: For comparing values. ReturnsTrue
orFalse
, does not raise an exception.validate_
: ReturnsNone
or raises an exception if something is wrong.from_
: For class methods that instantiate an object based on the given input data.
For other functionality, choose names that clearly communicate intent without being overly verbose. Focus on what the method does, not on how the method does it.
Imports¶
Import sequencing is handled with isort. We follow these additional rules:
Standard Library Imports¶
Standard libraries should be imported absolutely and without aliasing. Importing the library aids readability, as other users may be familiar with that library.
# Good import os os.stat('.') # Bad from os import stat stat('.')
Internal Imports¶
Internal imports are those between two modules inside the same Vyper package.
- Internal imports may use either
import
orfrom ..
syntax. The imported value shoould be a module, not an object. Importing modules instead of objects avoids circular dependency issues.- Internal imports may be aliased where it aids readability.
- Internal imports must use absolute paths. Relative imports cause issues when the module is moved.
# Good import vyper.ast.nodes as nodes from vyper.ast import nodes # Bad, `get_node` is a function from vyper.ast.nodes import get_node # Bad, do not use relative import paths from . import nodes
Cross-Package Imports¶
Cross-package imports are imports between one Vyper package and another.
- Cross-package imports must not request anything beyond the root namespace of the target package.
- Cross-package imports may be aliased where it aids readability.
- Cross-package imports may use
from [module] import [package]
syntax.# Good from vyper.ast import fold from vyper import ast as vy_ast # Bad, do not import beyond the root namespace from vyper.ast.annotation import annotate_python_ast
Exceptions¶
We use custom exception classes to indicate what has gone wrong during compilation.
- All raised exceptions must use an exception class that appropriately describes what has gone wrong. When none fits, or when using a single exception class for an overly broad range of errors, consider creating a new class.
- Builtin Python exceptions must not be raised intentionally. An unhandled builtin exception indicates a bug in the codebase.
- Use
CompilerPanic
for errors that are not caused by the user.
Strings¶
Strings substitutions should be performed via formatted string literals rather than the str.format
method or other techniques.
Type Annotations¶
- All publicly exposed classes and methods should include PEP 484 annotations for all arguments and return values.
- Type annotations should be included directly in the source. Stub files may be used where there is a valid reason. Source files using stubs must still be annotated to aid readability.
- Internal methods should include type annotations.
Tests¶
We use the pytest framework for testing, and eth-tester for our local development chain.
Best Practices¶
pytest
functionality should not be imported withfrom ...
style syntax, particularlypytest.raises
. Importing the library itself aids readability.- Tests must not be interdependent. We use
xdist
to execute tests in parallel. You cannot rely on which order tests will execute in, or that two tests will execute in the same process.- Test cases should be designed with a minimalistic approach. Each test should verify a single behavior. A good test is one with few assertions, and where it is immediately obvious exactly what is being tested.
- Where logical, tests should be parametrized or use property-based testing.
- Tests must not involve mocking.
Directory Structure¶
Where possible, the test suite should copy the structure of main Vyper package. For example, test cases for vyper/context/types/
should exist at tests/context/types/
.
Filenames¶
Test files must use the following naming conventions:
test_[module].py
: When all tests for a module are contained in a single file.test_[module]_[functionality].py
: When tests for a module are split across multiple files.
Fixtures¶
- Fixtures should be stored in
conftest.py
rather than the test file itself.conftest.py
files must not exist more than one subdirectory beyond the initialtests/
directory.- The functionality of a fixture must be fully documented, either via docstrings or comments.
Documentation¶
It is important to maintain comprehensive and up-to-date documentation for the Vyper language.
- Documentation must accurately reflect the current state of the master branch on Github.
- New functionality must not be added without corresponding documentation updates.
Writing Style¶
We use imperative, present tense to describe APIs: “return” not “returns”. One way to test if we have it right is to complete the following sentence:
“If we call this API it will: …”
For narrative style documentation, we prefer the use of first-person “we” form over second-person “you” form.
Additionally, we recommend the following best practices when writing documentation:
- Use terms consistently.
- Avoid ambiguous pronouns.
- Eliminate unneeded words.
- Establish key points at the start of a document.
- Focus each paragraph on a single topic.
- Focus each sentence on a single idea.
- Use a numbered list when order is important and a bulleted list when order is irrelevant.
- Introduce lists and tables appropriately.
Google’s technical writing courses are a valuable resource. We recommend reviewing them before any significant documentation work.
API Directives¶
- All API documentation must use standard Python directives.
- Where possible, references to syntax should use appropriate Python roles.
- External references may use intersphinx roles.
Internal Documentation¶
Internal documentation is vital to aid other contributors in understanding the layout of the Vyper codebase.
We handle internal documentation in the following ways:
- A
README.md
must be included in each first-level subdirectory of the Vyper package. The readme explain the purpose, organization and control flow of the subdirectory.- All publicly exposed classes and methods must include detailed docstrings.
- Internal methods should include docstrings, or at minimum comments.
- Any code that may be considered “clever” or “magic” must include comments explaining exactly what is happening.
Docstrings should be formatted according to the NumPy docstring style.
Commit Messages¶
Contributors should adhere to the following standards and best practices when making commits to be merged into the Vyper codebase.
Maintainers may request a rebase, or choose to squash merge pull requests that do not follow these standards.
Conventional Commits¶
Commit messages should adhere to the Conventional Commits standard. A convetional commit message is structured as follows:
<type>[optional scope]: <description>
[optional body]
[optional footer]
The commit contains the following elements, to communicate intent to the consumers of your library:
- fix: a commit of the type
fix
patches a bug in your codebase (this correlates withPATCH
in semantic versioning).- feat: a commit of the type
feat
introduces a new feature to the codebase (this correlates withMINOR
in semantic versioning).- BREAKING CHANGE: a commit that has the text
BREAKING CHANGE:
at the beginning of its optional body or footer section introduces a breaking API change (correlating withMAJOR
in semantic versioning). A BREAKING CHANGE can be part of commits of any type.
The use of commit types other than fix:
and feat:
is recommended. For example: docs:
, style:
, refactor:
, test:
, chore:
, or improvement:
. These tags are not mandated by the specification and have no implicit effect in semantic versioning.
Best Practices¶
We recommend the following best practices for commit messages (taken from How To Write a Commit Message):
- Limit the subject line to 50 characters.
- Use imperative, present tense in the subject line.
- Capitalize the subject line.
- Do not end the subject line with a period.
- Separate the subject from the body with a blank line.
- Wrap the body at 72 characters.
- Use the body to explain what and why vs. how.