Skip to content

Style Guide

Lukas Masuch edited this page Jun 10, 2024 · 13 revisions

Streamlit uses code autoformatters on presubmit. So most of the style-related work is automatically done for you. For everything else, there's this page.

Every language

Highest priority

  • Write tests! It is very rare that any contribution will be accepted without tests.
  • After tests, the most important thing is readability! This means:
    • Name functions and variables in such a way that you don't need comments to explain the code. Example:
      # Bad:
      // If going back to 0, set transition to none.
      // If the time between the previous update and this one was < 50ms, set transition to none.
      const status =
        value < this.lastValue || time - this.lastTime < 50 ? "reset" : "animate"
      
      # Good:
      const isMovingBackwards = value < this.lastValue
      const isMovingSuperFast = time - this.lastTime < FAST_UPDATE_MS
      const className = isMovingBackwards || isMovingSuperFast ?
          "without-transition" : "with-transition"
    • When a function or if/for/while/try/etc block is getting large, factor out parts of the code into sub-functions with nice names. Example:
      # Bad:
      def run(script_path, command_line, args):
          # Fix sys path:
          sys.path.insert(0, os.path.dirname(script_path))
      
          # Fix matplotlib crash:
          if sys.platform == "darwin" and config.get_option("runner.fixMatplotlib"):
              try:
                  import matplotlib
      
                  matplotlib.use("Agg")
              except ImportError:
                  pass
      
          # Fix sys argv:
          sys.argv = [script_path] + list(args)
      
          # Set up signal handler:
          def signal_handler(signal_number, stack_frame):
              # The server will shut down its threads and stop the ioloop
              Server.get_current().stop()
      
          signal.signal(signal.SIGTERM, signal_handler)
          signal.signal(signal.SIGINT, signal_handler)
          if sys.platform == "win32":
              signal.signal(signal.SIGBREAK, signal_handler)
          else:
              signal.signal(signal.SIGQUIT, signal_handler)
      
      # Good:
      def run(script_path, command_line, args):
          _fix_sys_path(script_path)
          _fix_matplotlib_crash()
          _fix_sys_argv(script_path, args)
          _set_up_signal_handler()
  • Avoid inheritance (prefer composition)
  • Avoid methods (prefer non-class functions, or static). Only use methods if this or self is needed. And even then, if all you need is to read a couple of values from this, prefer passing them as parameters to a non-class function: foo(this.value1, this.value2). This makes it clear that foo() does not mutate this.
  • Ask before introducing new dependencies!
  • Consider splitting it up your pull request. Sometimes our pull requests get really big. We recommend pull requests to be a maximum of 250 lines of code changed.

Normal priority

  • Set up your editor to use our .editorconfig file (see editorconfig.org)
  • You must include the Streamlit license header in all files. The easiest what to do that when creating a new file is to call make headers.
  • Never leave commented-out code in the codebase.
  • Capitalize comments, use proper grammar and punctuation, and no cursing.
  • In comments and docstrings: only 1 space after a period!

Python

We use PEP8 style for Python code, with a few adjustments:

  • Never use assert to check for user errors (e.g. checking for inputs to a function) since they are skipped when Python optimizations are used. Use if plus raise instead.
    • It's OK to use assert to check for Streamlit-internal code, though.
  • Inside a module, anything that is declared at the root level MUST be prefixed with a _ if it's only used inside that module. (That is, anything private must start with _)

Filenames

Per PEP8, Python filenames should all be snake-cased regardless of what they contain.

Here are some examples of proper filenames (with counter-examples in parentheses):

  • bootstrap.py
  • case_converters.py (not "CaseConverters.py")
  • script_request_queue.py (not "ScriptRequestQueue.py")
  • forward_msg_cache_test.py (not "ForwardMsgCache_test.py")

Make sure folder names follow the convention as well. Aim to avoid underscores whenever possible. For example:

  • lib/tests/watcher (Great)
  • lib/tests/echotestdata (Great)
  • lib/tests/echo_test_data (OK)

Imports

  • Imports should be split into groups, in this order:
    1. Standard library
    2. 3rd party
    3. Streamlit
  • Imports within each group should be sorted
  • Imports should not wrap (makes them easier to sort)
  • Don’t import items from modules; import the entire module instead:
    • WRONG: from streamlit.mymodule import internal_function
    • RIGHT: from streamlit import mymodule
  • The exception to the above is classes. You should import them this way:
    • RIGHT: from streamlit.MyClass import MyClass

Docstrings

  • Use Numpydoc style.
  • Docstrings are meant for users of a function, not developers who may be edit the internals of that function in the future. If you want to talk to future developers, use comments.
  • All modules that we expect users to interact with must have top-level docstrings. If a user is not meant to interact with a module, docstrings are optional.
  • A docstring should not be a simple re-statement of the function name / class name / filename. For example, this is a bad docstring for DeltaGenerator.py: "Declares the DeltaGenerator class".

Python boilerplate

Every Python file in Streamlit should start like this:

# Copyright etc etc etc

"""This is the docstring, if any."""

Of course, if your file needs a shebang, add it to the top of your file and move everything else down by one line accordingly.

Logging and printing

The main principle here is "anything the user may want to able to easily pipe into a file / another process should go into stdout, everything else stderr".

This means you should always have logs, warnings, errors, and notices end up in stderr. Never stdout.

TODO: come up with clear rules about what gets printed using click color prompts, and what gets printed via logs.

JavaScript / TypeScript

  • We use the AirBNB style for JavaScript and TypeScript.

  • Don't end lines with semi-colons

  • Before adding a new JS dependency, check its license!

    • Some licenses that are compatible with ours are: Apache, MIT, BSD, public domain, unlicense, and Creative Commons without share-alike!.
    • Incompatible licenses: GPL, AGPL, Creative Commons with share-alike
  • After adding a new JS dependency, rerun make notices to rebuild the NOTICES file.

  • Never use console.log or similar functions in final code. Instead use logMessage() and other methods from lib/log. This will make logs available in dev mode but not in prod — so the final user's console is always clean. And, in the future, we'll likely make logMessage() add logs to a buffer so users can send bug reports with logs with a single click in the future.

  • If your code has values that depend on CSS styles, import them from SCSS styles directly with import { SCSS_VARS } from "autogen/scssVariables"

Styling Components - CSS-in-JS

  • We have removed almost all of the CSS/SCSS in our code, so please do not create new files/(S)CSS styles.
    • The only exception to use it is globally, which really should not apply.
  • Take a look at the base theme file. This file encompasses many styling presets. You are welcome to check out the primitives folder to see all the values.
  • When editing or creating a new component that needs styling, add those styles in the form of styled components in an accompanying styled-components.ts file.
    • We use Emotion Version 10 to style our components
    • All styled components begin with the word Styled to indicate it's a styled component.
    • We use Object Styles where possible to leverage TypeScript's benefits.
    • Utilize props in styled components to display elements that may have some interactivity.
      • Where possible, avoid the need to target other components. Sometimes, targeting outside components is necessary, but we want to try to avoid the interconnectedness between these components where possible.
    • Use the theme everywhere. Each styled component has a function input with a theme parameter (in addition to any props). The theme argument will be equivalent in structure to the main theme. Use the proper values that match the color/spacing/sizing/font style you are looking for.
      • Sometimes, the theme won't provide a valid value. That is fine, hard code the value if needed in either the Theme (if it can be generalized) or in the styled component (if it is special case)
  • When using BaseWeb, our design system library, be sure to import our theme in and use those values in overrides.
  • When using icons, check for their support in Emotion Icons

Assets

  • If you need to add image assets, fonts, etc, first check with Streamlit developers (and @tvst in particular) so we can look at the license. If all is good, you'll still need to add a line to the notices rule in our Makefile, and rerun make notices.