This document contains our style guides for the primary languages used by FiftyOne.
Our priority is consistency, so that developers can quickly ingest and understand the entire codebase without being distracted by style idiosyncrasies.
When in doubt, follow the existing style in the files you're contributing to.
Our Python style is derived from Google Python style, with the exception that we do not use type annotations.
All Python code is formatted with black and pylint via pre-commit hooks, which automatically enforces much of the whitespace-related components of our style.
Here are some highlights of our Python style:
-
Maximum line length is 79 characters, with the exception of long URLs that cannot be split
-
Indent your code with 4 spaces. That is, no tabs!
-
Always strip all trailing whitespace from lines that you edit (you can configure your text editor to do this automatically)
-
Follow standard typographic rules for spaces around punctuation
-
Leave two blank lines between top-level definitions, and one blank line between class method definitions
-
Imports should always be on separate lines at the top of the file, just after the module docstring. Imports should be grouped by type with one space between each group, sorted alphabetically within each group, with the groups sorted in order of most generic (standard library) to least generic (local modules)
-
Names should follow the conventions:
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
-
All private variables, constants, functions, methods, and classes should have
_
prepended -
All non-trivial public module/class methods should have docstrings describing their behavior, inputs, outputs, and exceptions (when applicable)
-
If a class inherits from no other base classes, explicitly inherit from
object
-
When encountering a pylint error during a commit that cannot be addressed for whatever reason, add an inline comment
# pylint: disable=rule
whererule
is the rule in question -
Use
@todo
to mark todo items in the source code when appropriate
Imports should always be on separate lines at the top of the file, immediately below the module docstring. Imports should be grouped by type with one space between each group, with the groups sorted in order of most generic to least generic:
- Standard library imports (most generic)
- Third-party package dependencies
- Voxel51-authored non-FiftyOne package dependencies
- FiftyOne modules (least generic)
For core FiftyOne imports, we import modules as fox
, where x
is the first
letter of the module imported. If necessary, we use foxy
to disambiguate
between two modules that start with the same letter.
Within each import group, imports should be sorted alphabetically by full
package path, ignoring from
and import
.
We also allow direct importing of (a small number of) names into the local namespace at the developer's discretion.
For example, an import block might look like this:
"""
Module docstring.
| Copyright 2017-2024, Voxel51, Inc.
| `voxel51.com <https://voxel51.com/>`_
|
"""
import os
import sys
import cv2
import numpy as np
import eta.core.image as etai
import eta.core.video as etav
import fiftyone as fo
from fiftyone.core.document import Document
import fiftyone.core.labels as fol
import fiftyone.core.media as fom
import fiftyone.core.metadata as fome
import fiftyone.utils.image as foui
import fiftyone.utils.image as fouv
Our docstring style is derived from Google Python style, with the exception that we do not use type annotations.
Note that the docstrings of all public methods and classes are automatically included in the documentation via Sphinx and Sphinx-Napoleon, so docstrings should use Sphinx constructs to link to relevant classes, functions, and docs sections as appropriate:
def example_function(foo, hello="world"):
"""An example docstring that uses Sphinx constructs.
Docs sections can be referenced via :ref:`custom text here <anchor-link>`.
Classes can be referenced via
:class:`fiftyone.core.expressions.ViewExpression` or
:class:`ViewExpression <fiftyone.core.expressions.ViewExpression>`.
Functions can be referenced via
:func:`fiftyone.core.datasets.list_datasets` or
:func:`list_datasets() <fiftyone.core.datasets.list_datasets>`.
.. note::
Directives like notes can be used.
Examples::
# Example code is encouraged!
import fiftyone as fo
import fiftyone.zoo as foz
dataset = foz.load_zoo_dataset("quickstart")
session = fo.launch_app(dataset)
Args:
foo: a required parameter
hello ("world"): a parameter with a default value
Returns:
some cool stuff
"""
pass
Remember to build the docs locally and verify that any docstrings that you're unsure about are properly rendered in the docs.
All modules should begin with a docstring that includes a short (usually one line) description and a copyright block formatted exactly as shown below:
"""
Short module description here (generally one sentence max).
A longer section can also be added here as appropriate.
| Copyright 2017-2024, Voxel51, Inc.
| `voxel51.com <https://voxel51.com/>`_
|
"""
All non-trivial public functions should have docstrings following the pattern shown below.
Each section can be omitted if there are no inputs, outputs, or no notable exceptions raised, respectively.
def to_polyline(detection, tolerance=2, filled=True):
"""Returns a :class:`fiftyone.core.labels.Polyline` representation of the
:class:`fiftyone.core.labels.Detection`.
If the detection has a mask, the returned polyline will trace the
boundary of the mask; otherwise, the polyline will trace the bounding
box itself.
Args:
detection: a :class:`fiftyone.core.labels.Detection`
tolerance (2): a tolerance, in pixels, when generating an
approximate polyline for the instance mask. Typical values are
1-3 pixels
filled (True): whether the polyline should be filled
Returns:
a :class:`fiftyone.core.labels.Polyline`
Raises:
ValueError: if the detection's bounding box has negative area
"""
pass
All non-trivial public classes should have class docstrings following the pattern shown below.
Note that the parameters of the __init__()
method are documented in the class
docstring itself:
class Classification(object):
"""A classification label.
Args:
label: the label string
confidence (None): a confidence in ``[0, 1]`` for the classification
"""
def __init__(self, label, confidence=None):
pass
FiftyOne uses the standard Python logging
module to log messages using the
conventions below:
import logging
# Use this pattern to retrieve a logger for the current module
logger = logging.getLogger(__name__)
logger.debug(...)
logger.info(...)
logger.warning(...)
# Use native exceptions rather than `logger.error()` or `logger.critical()`
raise Exception(...)
You can also use the warnings
package if you want to issue a warning inside a
loop but want to avoid spamming the user's logs/stdout:
import warnings
for _ in range(100):
msg = "This warning should only be printed once"
warnings.warn(msg)
You don't customize black, silly! From the docs:
Pro-tip: If you’re asking yourself “Do I need to configure anything?” the answer is “No”. Black is all about sensible defaults.
With that said, we do maintain a limited configuration in the [tool.black]
section of pyproject.toml
.
To permanently disable a pylint message, add it to the disable
field in the
pylintrc
file:
[MESSAGES CONTROL]
disable=too-few-public-methods,too-many-arguments
To disable a pylint message for the rest of the current block (indentation level) in a module, add the comment:
# pylint: disable=too-many-instance-attributes
To disable a pylint message for the current line:
from builtins import * # pylint disable=wildcard-import
To disable pylint errors temporarily in a module:
# pragma pylint: disable=redefined-builtin
# pragma pylint: enable=wildcard-import
from builtins import *
# pragma pylint: enable=redefined-builtin
# pragma pylint: enable=wildcard-import
See the pylint user guide for more information.
The App is a TypeScript monorepo in which the main package is a React 18 application.
Comprehensive Style guide forthcoming.
We are introducing ESLint to enforce linting rules. Since this is a rather late introduction, we're using the only-warn plugin to render all errors as warnings until we gracefully address all warnings in small chunks, after which this plugin will be removed and rules will be enforced with higher strictness.
See app/.eslintrc.js
for our ESLint config, and refer to
this page for
configuration options.
TypeScript and CSS files in FiftyOne are formatted with prettier via pre-commit hooks, which automatically enforces much of the whitespace-related components of our style.
See .prettierrc.js
for our prettier configuration, and refer to
this page for configuration
options.
The FiftyOne Documentation is written primarily in reStructuredText (RST) files and rendered via Sphinx and Sphinx-Napoleon.
Note that the docstrings of all public methods and classes in the Python code are automatically included in the documentation. See this section for more information about writing Sphinx-friendly docstrings.
RST files in FiftyOne are formatted with prettier via pre-commit hooks, which automatically enforces much of the whitespace-related components of our style.
See .prettierrc
for our prettier configuration, and refer to
this page for configuration
options.
Here are some highlights of our RST style:
-
Maximum line length is 79 characters, with the exception of long URLs that cannot be split
-
Leave exactly one blank line before and after all of the following:
- Paragraphs
- Code blocks
- Section and subsection headings
-
One blank line at the end of the file
-
Indent 4 spaces when writing multiline list items, tabs, groups, code blocks, etc.
-
Always strip all trailing whitespace from lines that you edit (you can configure your text editor to do this automatically)
-
Follow standard typographic rules for spaces around punctuation
-
Add anchor links for any sections that you link to from other pages, so that section titles can be changed later if desired:
.. _view-stages:
View stages
___________
Dataset views encapsulate a pipeline of logical operations that determine which
samples appear in the view (and perhaps what subset of their contents).
- Never edit an anchor link itself once it is created, as this may break an external link (e.g., blog post, presentation, etc.)
Always build the docs locally to verify that your edits render correctly before committing your changes.
Markdown files in FiftyOne should be written in GitHub-flavored Markdown.
Markdown files in FiftyOne are formatted with prettier via pre-commit hooks, which automatically enforces much of the whitespace-related components of our style.
See .prettierrc
for our prettier configuration, and refer to
this page for configuration
options.
Here are some highlights of our Markdown style:
-
Maximum line length is 79 characters, with the exception of long URLs that cannot be split
-
All Markdown files start with a title with
# Uppercase Title Words
syntax -
Section headings use
## Capital then lowercase
syntax -
Leave exactly one blank line before and after all of the following:
- Paragraphs
- Code blocks
- Section headings
##
- Lower level headings
###
and####
-
One blank line at the end of the file
-
Indent 4 spaces when writing multiline list items
-
Always strip all trailing whitespace from lines that you edit (you can configure your text editor to do this automatically)
-
Follow standard typographic rules for spaces around punctuation
It is highly recommended that you install the grip package, which lets you render your Markdown files locally on your machine using GitHub's API to ensure that the file will render correctly at https://github.com.
You can render a Markdown file with grip
by running:
grip -b --user=<github-user> --pass=<api-key> /path/to/markdown/file.md
See .prettierrc
for our prettier configuration, and refer to
this page for configuration
options.