Skip to content

Conditions

The condition field filters pattern matches. It applies conditions to captures like so:

rule:
  - id: no-global-variables
    pattern: ${var} = ${value}
    condition: |
      var.in_module_scope()
        and not var.is_upper_case()
        and not var.starts_with("_")
    description: Don't declare `${var}` as a global variable

Listed below are all conditions available to use:

Name Conditions

Conditions related to capture names.

Note that all name conditions are themselves named in snake_case instead of the standard Python naming for str functions which uses no underscores.

Python str function Sourcery condition
str.islower capture.is_lower_case
str.isupper capture.is_upper_case
str.startswith capture.starts_with
str.endswith capture.ends_with

This is so that all Sourcery conditions are consistently named, and also allows easy addition of conditions like is_snake_case which is much better named than issnake 🐍!

capture.matches_regex(pattern: str) -> bool

Returns true if the regex pattern is found anywhere in capture.

This search is unanchored meaning it matches anywhere in the string:

rules:
- id: dont-use-long-numbers-in-vars
  description: Don't use long numbers in variable names
  pattern: ${name} = ${value}
  condition: name.matches_regex(r"\d\d\d")  # Use raw strings to handle backslashes
  tests:
  - match: start123end = 123
  - match: begin123 = "BAD"
  - no-match: a12b34c56 = "OK"

To anchor the search:

  • Use ^ to anchor at the start of the string
  • Use $ to anchor at the end of the string
rules:
- id: dont-use-dunder-vars
  description: Don't use dunder name `${name}` as a variable name
  pattern: ${name} = ${value}
  condition: name.matches_regex("^__.*__$")
  tests:
  - match: __strange__ = 123
  - match: __eq__ = "WRONG"
  - no-match: a__b__c = "OK"

capture.is_lower_case() -> bool

Return True if all cased characters in the name are lower case.

rules:
- id: lower-names
  description: Ensure variable name is lower case
  pattern: ${var} = {$value}
  condition: var.is_lower_case()
  tests:
  - match: banana = 1
  - match: banana2 = 1
  - no-match: BANANA = 1
  - no-match: baNana = 1

capture.is_upper_case() -> bool

Return True if all cased characters in the name are upper case.

rules:
- id: upper-names
  description: Ensure variable name is upper case
  pattern: ${var} = {$value}
  condition: var.is_upper_case()
  tests:
  - match: BANANA = 1
  - match: BANANA2 = 1
  - no-match: banana = 1
  - no-match: BAnANA = 1

left.equals(right: Capture | str) -> bool

Returns true if left and right names are the same.

rules:
- id: use-standard-name-for-aliases-pandas
  description: Import `pandas` as `pd`
  pattern: import ${left*}, pandas as ${alias}, ${right*}
  condition: not alias.equals("pd")
  tests:
  - match: import pandas as pds
  - match: import pandas as np
  - match: import numpy, pandas as pds, tensorflow
  - no-match: import pandas
  - no-match: import pandas as pd
  - no-match: import numpy, pandas as pd, tensorflow
  - no-match: import modin.pandas as pds

capture.character_count() -> int

Counts the number of characters.

Can be used to limit the length of a name:

rules:
- id: limit-var-name-length
  description: Variables longer than 20 characters are not allowed
  pattern: ${var} = ${value}
  condition: var.character_count() <= 20
  test:
  - match: the_meaning_of_life_the_universe_and_everything = 42
  - no-match: concise_name = 43

capture.starts_with(prefix: str) -> bool

Returns true if the name starts with the specified prefix.

rules:
- id: avoid-global-variables
  description: Do not define variables at the module level
  pattern: "${var} = ${value}"
  condition: |
    var.in_module_scope()
      and not var.is_upper_case()
      and not var.starts_with("_")
  tests:
  - match: max_holy_handgrenade_count = 3
  # The next example is wrapped in a string because `:` has a special meaning in yaml
  - match: "max_holy_handgrenade_count: int = 3"
  - no-match: _max_holy_handgrenade_count = 3
  - no-match: MAX_HOLY_HANDGRENADE_COUNT = 3
  - no-match: |
      def f():
          max_holy_handgrenade_count = 3

capture.ends_with(suffix: str) -> bool

Returns true if the name ends with the specified suffix.

rules:
- id: name-type-suffix
  description: Don't use the type of a variable as a suffix.
  explanation: |
    Names shouldn't needlessly include the type of the variable.
  pattern: ${name} = ${value}
  condition: |
    name.ends_with("_dict")
    or name.ends_with("_list")
    or name.ends_with("_set")
    or name.ends_with("_int")
    or name.ends_with("_float")
    or name.ends_with("_str")
  tests:
  - match: magic_int = 42
  - match: magic_int = 42.00
  - match: magic_float = 42
  - match: magic_float = 42.00
  - match: custom_notes_dict = {}

Scope Conditions

Contains conditions related to the scope a capture is in.

All condition only check if the innermost local scope (function, class or module) matches the condition.

See Python Scopes and Namespace for more details about scopes.

capture.in_module_scope() -> bool

Returns true if the capture is found in module scope.

This means it is not within a class or a function definition.

rules:
- id: no-assignment-in-module-scope
  pattern: ${var} = ${value}
  condition: var.in_module_scope()
  tests:
  - match: |
      # This var is not in a class or function so is in module scope
      x = 0
  - no-match: |
      class Example:
          # This var is in a class so is not in module scope
          x = 0
  - no-match: |
      def f():
          # This var is in a function so is not in module scope
          x = 0

Type Conditions

Contains conditions related to Capture types.

capture.has_type(*type: str) -> bool

Return true if the capture's inferred type is type.

Multiple arguments can be passed to check any of them match.

rules:
- id: dont-use-str-function-on-str-type
  description: Unneccesary call to `str` on value with `str` type
  pattern: str(${value})
  condition: value.has_type("str")
  tests:
  - match: |
      name = "Ada"
      full_name = str(name) + "Lovelace"
    no-match: |
      count = 6
      message = "Found " + str(count) + "nectarines"