├── .github └── workflows │ └── sync-rules.yml ├── LICENSE ├── README.md ├── google-python-style-guide.yaml ├── remove-debugging-statements.yaml └── use-fstring.yaml /.github/workflows/sync-rules.yml: -------------------------------------------------------------------------------- 1 | name: Sync public rules 2 | 3 | # See copybara GH action docs here for syncing a monorepo subdirectory to 4 | # the root directory in another repo 5 | # https://github.com/Olivr/copybara-action/blob/main/docs/basic-usage.md#subfolder-as-destinations-root 6 | 7 | on: 8 | - pull_request_target 9 | - push 10 | 11 | jobs: 12 | sync-repo: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | with: 17 | fetch-depth: 0 18 | 19 | - uses: olivr/copybara-action@v1.2.3 20 | with: 21 | ssh_key: ${{ secrets.SYNC_RULES_SSH_KEY }} 22 | access_token: ${{ secrets.SYNC_RULES_GH_TOKEN }} 23 | sot_repo: sourcery-ai/core 24 | destination_repo: sourcery-ai/sourcery-rules 25 | push_include: sourcery/rules/public/** .github/workflows/sync-rules.yml 26 | pr_move: | 27 | ||sourcery/rules/public 28 | sourcery/rules/public/.github||.github 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | “Commons Clause” License Condition v1.0 2 | 3 | The Software is provided to you by the Licensor under the License, as defined below, subject to the following condition. 4 | 5 | Without limiting other conditions in the License, the grant of rights under the License will not include, and the License does not grant to you, the right to Sell the Software. 6 | 7 | For purposes of the foregoing, “Sell” means practicing any or all of the rights granted to you under the License to provide to third parties, for a fee or other consideration (including without limitation fees for hosting or consulting/ support services related to the Software), a product or service whose value derives, entirely or substantially, from the functionality of the Software. Any license notice or attribution required by the License must also include this Commons Clause License Condition notice. 8 | 9 | Software: sourcery-rules (https://github.com/sourcery-ai/sourcery-rules) 10 | License: LGPL 2.1 (GNU Lesser General Public License, Version 2.1) 11 | Licensor: Sourcery.AI Limited (https://sourcery.ai/) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sourcery Rules 2 | 3 | This repo contains public rules definitions for Sourcery 4 | [optional rules](https://docs.sourcery.ai/Reference/Optional-Rules/). 5 | 6 | - Your contributions are welcome! 😃 Open a PR and share your cool rules here. 7 | - The [Sourcery team](https://sourcery.ai/about/) continuously adds new rules as 8 | well. 9 | - Are you having a problem where a Sourcery rule might be a good solution but 10 | not sure how? Open an issue here. 11 | 12 | ## Enabling Optional Sourcery Rules 13 | 14 | The rules in this repository are bundled with each Sourcery release. You can use 15 | them in the 16 | [`review` command of the Sourcery CLI](https://docs.sourcery.ai/Reference/Command-Line/review/) 17 | and add them to 18 | [your project's configuration](https://docs.sourcery.ai/Reference/Configuration/sourcery-yaml/). 19 | 20 | ### Configure Sourcery to Use Optional Rules 21 | 22 | You can use the `enable` configuration option in 23 | [your project's configuration file](https://docs.sourcery.ai/Reference/Configuration/sourcery-yaml/), 24 | to enable a rule or a tag. 25 | 26 | In your project's `.sourcery.yaml` file: 27 | 28 | ```yaml 29 | rule_settings: 30 | enable: 31 | - default # Continue to enable the default rules enabled 32 | - gpsg-import 33 | - gpsg-naming-pep8 34 | - no-long-functions 35 | ``` 36 | 37 | ### Use Optional Rules in the Command Line 38 | 39 | You can also run the 40 | [`review` command](https://docs.sourcery.ai/Reference/Command-Line/review/) of 41 | the Sourcery CLI with optional rules using the (`--enable` 42 | option)\[https://docs.sourcery.ai/Reference/Command-Line/review/#-enable\]. 43 | 44 | This is a great way to experiment when you're considering introducing some 45 | optional rules to your project. 46 | 47 | To find out how many issues to Google Python Style Guide would flag: 48 | 49 | ```sh 50 | sourcery review --enable gpsg . 51 | ``` 52 | 53 | You can also run a subset of the Google Python Style Guide Rules. For example, 54 | if you want to find out which of your modules, classes, and public functions are 55 | missing a docstring: 56 | 57 | ```sh 58 | sourcery review --enable gpsg-docstrings . 59 | ``` 60 | 61 | You can also run `review` with a single rule: 62 | 63 | ```sh 64 | sourcery review --enable avoid-global-variables --enable errors-named-error . 65 | ``` 66 | -------------------------------------------------------------------------------- /google-python-style-guide.yaml: -------------------------------------------------------------------------------- 1 | version: '1' 2 | 3 | rules: 4 | 5 | # ================================================================================== 6 | # [Google Python Style Guide rules](https://docs.sourcery.ai/Reference/Custom-Rules/gpsg/) 7 | # ================================================================================== 8 | # 9 | # The following rules instruct Sourcery to help making sure that the Google Python Style 10 | # Guide is respected in this project. 11 | # 12 | # The [Google Python Style Guide](https://google.github.io/styleguide/pyguide.html) was 13 | # created by [Google](https://github.com/google) and published under the 14 | # [CC BY 3.0](https://creativecommons.org/licenses/by/3.0/legalcode) license. 15 | 16 | # ---------------------------------------------------------------------------------- 17 | # 2.2 Import Rules 18 | # ---------------------------------------------------------------------------------- 19 | # 20 | # The rules in this section keep imports intuitive and consistent. 21 | # 22 | # Individual examples for these rules in action can be found at 23 | # [examples/individual_rules/import_rules.py](./examples/individual_rules/import_rules.py) 24 | 25 | - id: no-wildcard-imports 26 | pattern: from ... import * 27 | description: Do not use wildcard imports 28 | explanation: | 29 | Use import statements for packages and modules only, not for individual classes or functions. 30 | 31 | - Use `import x` for importing packages and modules. 32 | - Use `from x import y` where `x` is the package prefix and `y` is the module name with no prefix. 33 | - Use `from x import y as z` if two modules named `y` are to be imported, if `y` conflicts with a top-level name defined in the current module, or if `y` is an inconveniently long name. 34 | - Use `import y as z` only when `z` is a standard abbreviation (e.g., np for numpy). 35 | 36 | From: Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 37 | tags: 38 | - google-python-style-guide 39 | - gpsg 40 | - gpsg-import 41 | tests: 42 | - match: from numpy import * 43 | - match: from pandas.series import * 44 | - match: from .something import * 45 | - no-match: from math import sin 46 | 47 | - id: no-relative-imports 48 | description: Always use absolute imports instead of relative imports 49 | explanation: | 50 | Do not use relative names in imports. Even if the module is in the same package, use the full package name. This helps prevent unintentionally importing a package twice. 51 | 52 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 53 | pattern: from ${module} import ... 54 | condition: module.matches_regex(r"^\.") 55 | tags: 56 | - google-python-style-guide 57 | - gpsg 58 | - gpsg-import 59 | tests: 60 | - match: from . import important_function 61 | - match: from .my_module import important_function 62 | - match: from ..my_module import important_function, unimportant_function 63 | - no-match: from my_company.my_module import important_function 64 | - no-match: from pathlib import Path 65 | 66 | - id: use-standard-name-for-aliases-pandas 67 | description: Import `pandas` as `pd` 68 | explanation: | 69 | Use `import y as z` only when `z` is a standard abbreviation. 70 | 71 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 72 | 73 | pattern: import ..., pandas as ${alias}, ... 74 | condition: not alias.equals("pd") 75 | tags: 76 | - google-python-style-guide 77 | - gpsg 78 | - gpsg-standard-import-alias 79 | tests: 80 | - no-match: import pandas 81 | - no-match: from pandas import DataFrame 82 | - no-match: import pandas as pd 83 | - no-match: import numpy, pandas as pd, tensorflow 84 | - no-match: from modin import pandas as pds 85 | - no-match: import modin.pandas as pds 86 | - match: import pandas as pds 87 | - match: import pandas as np 88 | - match: import numpy, pandas as pds, tensorflow 89 | 90 | - id: use-standard-name-for-aliases-numpy 91 | description: Import `numpy` as `np` 92 | explanation: | 93 | Use `import y as z` only when `z` is a standard abbreviation. 94 | 95 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 96 | 97 | pattern: import ..., numpy as ${alias}, ... 98 | condition: not alias.equals("np") 99 | tags: 100 | - google-python-style-guide 101 | - gpsg 102 | - gpsg-standard-import-alias 103 | tests: 104 | - no-match: import numpy 105 | - no-match: from numpy import ndarray 106 | - no-match: import numpy as np 107 | - no-match: import pandas, numpy as np, tensorflow 108 | - match: import numpy as numpie 109 | - match: import numpy as pd 110 | - match: import pandas, numpy as numpie, tensorflow 111 | 112 | - id: use-standard-name-for-aliases-matplotlib-pyplot 113 | description: Import `matplotlib.pyplot` as `plt` 114 | explanation: | 115 | Use `import y as z` only when `z` is a standard abbreviation. 116 | 117 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 118 | 119 | pattern: import ..., matplotlib.pyplot as ${alias}, ... 120 | condition: not alias.equals("plt") 121 | tags: 122 | - google-python-style-guide 123 | - gpsg 124 | - gpsg-standard-import-alias 125 | tests: 126 | - no-match: import matplotlib.pyplot 127 | - no-match: from matplotlib.pyplot import Figure 128 | - no-match: import matplotlib.pyplot as plt 129 | - no-match: import pandas, matplotlib.pyplot as plt, tensorflow 130 | - match: import matplotlib.pyplot as mplplot 131 | - match: import pandas, matplotlib.pyplot as mplplot, tensorflow 132 | 133 | - id: use-standard-name-for-aliases-tensorflow 134 | description: Import `tensorflow` as `tf` 135 | explanation: | 136 | Use `import y as z` only when `z` is a standard abbreviation. 137 | 138 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 139 | 140 | pattern: import ..., tensorflow as ${alias}, ... 141 | condition: not alias.equals("tf") 142 | tags: 143 | - google-python-style-guide 144 | - gpsg 145 | - gpsg-standard-import-alias 146 | tests: 147 | - no-match: import tensorflow 148 | - no-match: from tensorflow import keras 149 | - no-match: import tensorflow as tf 150 | - no-match: import pandas, tensorflow as tf 151 | - match: import tensorflow as tflow 152 | - match: import pandas, tensorflow as tflow 153 | 154 | - id: use-standard-name-for-aliases-datetime 155 | description: Import `datetime` as `dt` 156 | explanation: | 157 | Use `import y as z` only when `z` is a standard abbreviation. 158 | 159 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 160 | 161 | pattern: import ..., datetime as ${alias}, ... 162 | condition: not alias.equals("dt") 163 | tags: 164 | - google-python-style-guide 165 | - gpsg 166 | - gpsg-standard-import-alias 167 | tests: 168 | - no-match: import datetime 169 | - no-match: from datetime import datetime 170 | - no-match: import datetime as dt 171 | - no-match: import pandas, datetime as dt 172 | - match: import datetime as dtime 173 | - match: import pandas, datetime as dtime, tensorflow 174 | 175 | - id: use-standard-name-for-aliases-tkinter 176 | description: Import `tkinter` as `tk` 177 | explanation: | 178 | Use `import y as z` only when `z` is a standard abbreviation. 179 | 180 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 181 | 182 | pattern: import ..., tkinter as ${alias}, ... 183 | condition: not alias.equals("tk") 184 | tags: 185 | - google-python-style-guide 186 | - gpsg 187 | - gpsg-standard-import-alias 188 | tests: 189 | - no-match: import tkinter 190 | - no-match: from tkinter import ttk 191 | - no-match: import tkinter as tk 192 | - no-match: import pandas, tkinter as tk 193 | - match: import tkinter as t 194 | - match: import pandas, tkinter as t, tensorflow 195 | 196 | - id: use-standard-name-for-aliases-multiprocessing 197 | description: Import `multiprocessing` as `mp` 198 | explanation: | 199 | Use `import y as z` only when `z` is a standard abbreviation. 200 | 201 | From Google Style Guide [2.2.4](https://google.github.io/styleguide/pyguide.html#224-decision) 202 | 203 | pattern: import ..., multiprocessing as ${alias}, ... 204 | condition: not alias.equals("mp") 205 | tags: 206 | - google-python-style-guide 207 | - gpsg 208 | - gpsg-standard-import-alias 209 | tests: 210 | - no-match: import multiprocessing 211 | - no-match: from multiprocessing import Pool 212 | - no-match: import multiprocessing as mp 213 | - no-match: import pandas, multiprocessing as mp 214 | - match: import multiprocessing as multi 215 | - match: import pandas, multiprocessing as multi, tensorflow 216 | 217 | # ---------------------------------------------------------------------------------- 218 | # 2.4 Exception Rules 219 | # ---------------------------------------------------------------------------------- 220 | # 221 | # The rule in this section ensures that exceptions are consistently named. 222 | # 223 | # Individual examples for this rule in action can be found at 224 | # [examples/individual_rules/exception_rules.py](./examples/individual_rules/exception_rules.py) 225 | 226 | - id: errors-named-error 227 | pattern: | 228 | class ${error}(${base}): 229 | ${statements*} 230 | condition: (base.is_exception_type() or base.matches_regex("[A-Z][a-zA-Z]*Error")) and not error.matches_regex("[A-Z][a-zA-Z]*Error") 231 | description: Exception names must end in Error 232 | explanation: | 233 | From Google Style Guide [2.4.4](https://google.github.io/styleguide/pyguide.html#244-decision) 234 | tags: 235 | - google-python-style-guide 236 | - gpsg 237 | tests: 238 | - match: | 239 | class Foo(ValueError): 240 | ... 241 | - match: | 242 | class ExampleException(CustomError): 243 | def __init__(self, msg): 244 | ... 245 | - match: | 246 | class InvalidName(Exception): 247 | ... 248 | - match: | 249 | class InvalidName(BaseException): 250 | ... 251 | - no-match: | 252 | class MyError(Exception): 253 | def __init__(self, msg): 254 | ... 255 | - no-match: | 256 | class NameError(AttributeError): 257 | ... 258 | - no-match: | 259 | class Dog(Mammal): 260 | ... 261 | 262 | # ---------------------------------------------------------------------------------- 263 | # 2.5 Global Variable Rules 264 | # ---------------------------------------------------------------------------------- 265 | # 266 | # The rule in this section prevents global variables from being defined. 267 | # 268 | # Individual examples for this rule in action can be found at 269 | # [examples/individual_rules/global_variable_rules.py](./examples/individual_rules/global_variable_rules.py) 270 | 271 | - id: avoid-global-variables 272 | pattern: ${var} = ${value} 273 | condition: pattern.in_module_scope() and var.is_lower_case() and not var.starts_with("_") and var.is_identifier() and not var.contains("logger") 274 | description: Do not define variables at the module level - (found variable ${var}) 275 | explanation: | 276 | Avoid global variables. 277 | 278 | If needed, global variables should be declared at the module level and made internal to the module by prepending an `_` to the name. External access to global variables must be done through public module-level functions. 279 | 280 | While module-level constants are technically variables, they are permitted and encouraged. For example: `MAX_HOLY_HANDGRENADE_COUNT = 3`. Constants must be named using all caps with underscores. 281 | 282 | From Google Style Guide [2.5.4](https://google.github.io/styleguide/pyguide.html#254-decision) 283 | tags: 284 | - google-python-style-guide 285 | - gpsg 286 | tests: 287 | - match: max_holy_handgrenade_count = 3 288 | - match: 'max_holy_handgrenade_count: int = 3' 289 | - no-match: holy_handgrenade[1] = 3 290 | - no-match: _max_holy_handgrenade_count = 3 291 | - no-match: HolyGrenades = Dict[str, Grenade] 292 | - no-match: MAX_HOLY_HANDGRENADE_COUNT = 3 293 | - no-match: | 294 | def f(): 295 | max_holy_handgrenade_count = 3 296 | 297 | # ---------------------------------------------------------------------------------- 298 | # 2.8 Default Iterator and Operator Rules 299 | # ---------------------------------------------------------------------------------- 300 | # 301 | # The rule in this section suggests the use of correct iterator and operator types. 302 | # 303 | # Individual examples for this rule in action can be found at 304 | # [examples/individual_rules/default_iterator_and_operator_rules.py](./examples/individual_rules/default_iterator_and_operator_rules.py) 305 | 306 | - id: do-not-use-has-key 307 | pattern: ${d}.has_key(${key}) 308 | condition: d.has_type("dict") 309 | replacement: ${key} in ${d} 310 | description: Replace Python 2 syntax `dict.has_key` with a Python 3 membership test 311 | explanation: | 312 | Use default iterators and operators for types that support them, like lists, dictionaries, and files. The built-in types define iterator methods, too. Prefer these methods to methods that return lists, except that you should not mutate a container while iterating over it. 313 | 314 | From Google Style Guide [2.8.4](https://google.github.io/styleguide/pyguide.html#284-decision) 315 | 316 | Note that the method `dict.has_key` is only available in Python 2, but Sourcery only 317 | works with Python 3 code. Hence, this rule prevents programmers from erroneously 318 | checking membership with `dictionary.has_key(key)`, suggesting instead the use of 319 | the correct Python 3 syntax `key in dictionary`. 320 | tags: 321 | - google-python-style-guide 322 | - gpsg 323 | tests: 324 | - match: | 325 | tasks_for_today: dict[str, Task] = get_tasks("today") 326 | is_week_day = tasks_for_today.has_key("work") 327 | expect: | 328 | tasks_for_today: dict[str, Task] = get_tasks("today") 329 | is_week_day = "work" in tasks_for_today 330 | - match: | 331 | def is_movie_in_database(movie: Movie, movies: Dict[Movie, MovieSpec]) -> bool: 332 | return movies.has_key(movie) 333 | expect: | 334 | def is_movie_in_database(movie: Movie, movies: Dict[Movie, MovieSpec]) -> bool: 335 | return movie in movies 336 | - match: | 337 | pro_users = { 338 | user.name: user 339 | for user in database.fetch(User) 340 | } 341 | if pro_users.has_key(get_current_user().name): 342 | print("You've signed in under the PRO subscription") 343 | else: 344 | print("Please sign up for PRO to use this awesome feature!") 345 | expect: | 346 | pro_users = { 347 | user.name: user 348 | for user in database.fetch(User) 349 | } 350 | if get_current_user().name in pro_users: 351 | print("You've signed in under the PRO subscription") 352 | else: 353 | print("Please sign up for PRO to use this awesome feature!") 354 | - no-match: | 355 | # Not sure of `random_object` is a dictionary 356 | random_object.has_key(random_key) 357 | - no-match: | 358 | # Custom type `Database` is not a dictionary 359 | database: Database = get_database() 360 | print(database.has_key("dinosaur")) 361 | 362 | # ---------------------------------------------------------------------------------- 363 | # 2.10 Lambda Function Rules 364 | # ---------------------------------------------------------------------------------- 365 | # 366 | # The rules in this section ensure that `lambda`s are kept simple and short, or 367 | # suggest the use of other, more Pythonic constructs. 368 | # 369 | # Individual examples for these rules in action can be found at 370 | # [examples/individual_rules/lambda_function_rules.py](./examples/individual_rules/lambda_function_rules.py) 371 | 372 | - id: lambdas-should-be-short 373 | description: Lambda functions should be kept to a single line 374 | explanation: | 375 | Okay to use them for one-liners. 376 | 377 | If the code inside the lambda function is longer than 60-80 chars, it’s probably better to define it as a regular nested function. 378 | 379 | From Google Style Guide [2.10.4](https://google.github.io/styleguide/pyguide.html#2104-decision) 380 | 381 | pattern: 'lambda ...: ${body}' 382 | condition: body.character_count() > 80 383 | tags: 384 | - google-python-style-guide 385 | - gpsg 386 | - gpsg-lambda 387 | tests: 388 | - no-match: 'lambda x: x**2' 389 | - no-match: 'lambda x, y: x**y' 390 | - match: 'lambda x: do_something_very_long_and_involved_with(x) - do_other_very_long_and_involved_things_with(x)' 391 | - match: 'lambda x, y, z: do_something_very_long_and_involved_with(x) - do_other_very_long_and_involved_things_with(y, z)' 392 | 393 | - id: map-lambda-to-generator 394 | pattern: 'map(lambda ${arg}: ${expr}, ${items})' 395 | replacement: (${expr} for ${arg} in ${items}) 396 | description: Replace mapping a lambda with a generator expression 397 | explanation: |2 398 | 399 | Prefer generator expressions over map() or filter() with a lambda. 400 | 401 | From Google Style Guide [2.10](https://google.github.io/styleguide/pyguide.html#210-lambda-functions) 402 | tags: 403 | - google-python-style-guide 404 | - gpsg 405 | - gpsg-lambda 406 | tests: 407 | - match: 'transformed_things = map(lambda x: x**2, things)' 408 | expect: transformed_things = (x**2 for x in things) 409 | - match: 'list(map(lambda x: x**2, things))' 410 | expect: list(x**2 for x in things) 411 | - no-match: 'filter(lambda x: x > x**2, things)' 412 | 413 | - id: filter-lambda-to-generator 414 | pattern: 'filter(lambda ${arg}: ${expr}, ${items})' 415 | replacement: (${arg} for ${arg} in ${items} if ${expr}) 416 | description: Replace filtering with a lambda with a generator expression 417 | explanation: | 418 | Prefer generator expressions over map() or filter() with a lambda. 419 | 420 | From Google Style Guide [2.10](https://google.github.io/styleguide/pyguide.html#210-lambda-functions) 421 | tags: 422 | - google-python-style-guide 423 | - gpsg 424 | - gpsg-lambda 425 | tests: 426 | - match: 'filtered_things = filter(lambda x: x > x**2, things)' 427 | expect: filtered_things = (x for x in things if x > x**2) 428 | - match: 'list(filter(lambda x: x > x**2, things))' 429 | expect: list(x for x in things if x > x**2) 430 | - no-match: 'map(lambda x: x**2, things)' 431 | 432 | # ---------------------------------------------------------------------------------- 433 | # 2.11 Conditional Expression Rules 434 | # ---------------------------------------------------------------------------------- 435 | # 436 | # The rule in this section ensures conditional expressions are kept short and simple. 437 | # 438 | # Individual examples for this rule in action can be found at 439 | # [examples/individual_rules/conditional_expression_rules.py](./examples/individual_rules/conditional_expression_rules.py) 440 | 441 | - id: no-complex-if-expressions 442 | description: Only use conditional expressions for simple cases 443 | explanation: |2 444 | 445 | Each portion [of the conditional expression] must fit on one line: `true-expression`, `if-expression`, `else-expression`. Use a complete if statement when things get more complicated. 446 | 447 | From Google Style Guide [2.11.4](https://google.github.io/styleguide/pyguide.html#2114-decision) 448 | pattern: ${value} if ${test} else ${default} 449 | condition: value.character_count() > 80 or test.character_count() > 80 or default.character_count() > 80 450 | tags: 451 | - google-python-style-guide 452 | - gpsg 453 | tests: 454 | - no-match: a = 1 if cond else 2 455 | - match: a = 1 if this_is_an_incredibly_long_condition_that_is_more_than_80_characters_long_no_joking_around else 2 456 | - match: a = this_is_an_incredibly_long_value_that_is_more_than_80_characters_long_no_joking_around if cond else 2 457 | - match: a = 1 if cond else this_is_an_incredibly_long_value_that_is_more_than_80_characters_long_no_joking_around 458 | - match: a = 1 if cond else this_is_an_incredibly_long_value + is_more_than_80_characters_long_no_joking_around() 459 | 460 | # ---------------------------------------------------------------------------------- 461 | # 2.17 Decorator Rules 462 | # ---------------------------------------------------------------------------------- 463 | # 464 | # The rule in this section prevents [`staticmethod`](https://docs.python.org/3/library/functions.html#staticmethod) from being used. 465 | # 466 | # Individual examples for this rule in action can be found at 467 | # [examples/individual_rules/decorator_rules.py](./examples/individual_rules/decorator_rules.py) 468 | 469 | - id: do-not-use-staticmethod 470 | pattern: | 471 | @staticmethod 472 | def ${name}(...): 473 | ... 474 | description: Do not use the staticmethod decorator 475 | explanation: | 476 | Never use staticmethod unless forced to in order to integrate with an API defined in an existing library. Write a module level function instead. 477 | 478 | From: Google Style Guide [2.17.4](https://google.github.io/styleguide/pyguide.html#2174-decision) 479 | tags: 480 | - google-python-style-guide 481 | - gpsg 482 | tests: 483 | - match: | 484 | @staticmethod 485 | def suggested_event(new_suggestion) -> str: 486 | pass 487 | - match: | 488 | @staticmethod 489 | def suggested_event(new_suggestion: bool) -> str: 490 | pass 491 | - no-match: | 492 | def suggested_event(new_suggestion) -> str: 493 | pass 494 | - no-match: | 495 | @staticmethod # Note that this will not currently trigger where there are other decorators 496 | @other_decorator 497 | def suggested_event(new_suggestion) -> str: 498 | pass 499 | 500 | # ---------------------------------------------------------------------------------- 501 | # 2.21 Type Annotation Rules 502 | # ---------------------------------------------------------------------------------- 503 | # 504 | # The rules in this section suggest the addition of type annotations to functions and 505 | # methods. 506 | # 507 | # Individual examples for these rules in action can be found at 508 | # [examples/individual_rules/type_annotations_rules.py](./examples/individual_rules/type_annotations_rules.py) 509 | 510 | - id: require-parameter-annotation 511 | pattern: | 512 | def ${name}(..., ${arg}: !!!=${default?}, ...): 513 | ... 514 | condition: | 515 | not name.starts_with("_") 516 | and not arg.equals("self") 517 | and not arg.equals("cls") 518 | and not arg.equals("*") 519 | and not arg.equals("/") 520 | paths: 521 | exclude: 522 | - test_*.py 523 | - '*_test.py' 524 | description: Annotate parameter `${arg}` in public function/method `${name}` with a type annotation 525 | explanation: | 526 | Adding type annotations has several benefits: 527 | 528 | 1. It improves the documentation of the function 529 | 2. It allows the function to be checked for correctness 530 | 3. It allows checking that the function callers are passing the correct params 531 | 532 | These [mypy docs](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#functions) describe how to 533 | annotate function arguments and return types. 534 | 535 | From Google Style Guide [2.21](https://google.github.io/styleguide/pyguide#221-type-annotated-code) 536 | tags: 537 | - google-python-style-guide 538 | - gpsg 539 | - gpsg-type-annotations 540 | tests: 541 | - match: | 542 | def add(a, b: int): 543 | return a + b 544 | - match: | 545 | def f(a=1): 546 | return a * 2 547 | - no-match: | 548 | def f() -> int: 549 | pass 550 | - no-match: | 551 | def f(a: int, b: str): 552 | pass 553 | - no-match: | 554 | def f(self, a: int, b: str): 555 | pass 556 | - no-match: | 557 | def f(cls, a: int, b: str): 558 | pass 559 | - no-match: | 560 | def f(a: int, *, b: str): 561 | pass 562 | - no-match: | 563 | def f(a: int, /, b: str): 564 | pass 565 | 566 | - id: require-return-annotation 567 | pattern: | 568 | def ${name}(...) -> !!!: 569 | ... 570 | condition: not name.starts_with("_") 571 | paths: 572 | exclude: 573 | - test_*.py 574 | - '*_test.py' 575 | description: Annotate public function/method `${name}` with a return type annotation 576 | explanation: | 577 | Adding type annotations has several benefits: 578 | 579 | 1. It improves the documentation of the function 580 | 2. It allows the function to be checked for correctness 581 | 3. It allows checking that the function callers are passing the correct params 582 | 583 | These [mypy docs](https://mypy.readthedocs.io/en/stable/cheat_sheet_py3.html#functions) describe how to 584 | annotate function arguments and return types. 585 | 586 | From Google Style Guide [2.21](https://google.github.io/styleguide/pyguide#221-type-annotated-code) 587 | tags: 588 | - google-python-style-guide 589 | - gpsg 590 | - gpsg-type-annotations 591 | tests: 592 | - match: | 593 | def f(): 594 | x() 595 | - no-match: | 596 | def f() -> int: 597 | x() 598 | - match: | 599 | def f(a: int, b: str): 600 | x() 601 | - no-match: | 602 | def f(something) -> int: 603 | x() 604 | 605 | # ---------------------------------------------------------------------------------- 606 | # 3.8 Docstring and Comment Rules 607 | # ---------------------------------------------------------------------------------- 608 | # 609 | # The rules in this section ensure that functions, methods, classes and modules contain 610 | # docstrings. 611 | # 612 | # Individual examples for these rules in action can be found at 613 | # [examples/individual_rules/docstring_and_comment_rules.py](./examples/individual_rules/docstring_and_comment_rules.py) 614 | 615 | - id: docstrings-for-classes 616 | pattern: | 617 | class ${c}(...): 618 | """!!!""" 619 | ... 620 | description: Public classes should have docstrings 621 | explanation: | 622 | All public classes should have a docstring describing the class. 623 | 624 | The class docstring should also document the class' public attributes. 625 | 626 | From Google Style Guide [3.8.4](https://google.github.io/styleguide/pyguide.html#384-classes) 627 | condition: not c.starts_with("_") 628 | paths: 629 | exclude: 630 | - test_*.py 631 | - '*_test.py' 632 | tags: 633 | - google-python-style-guide 634 | - gpsg 635 | - gpsg-docstrings 636 | tests: 637 | - match: | 638 | class CheeseShopAddress: 639 | ... 640 | - match: | 641 | class CheeseShopAddress(Address): 642 | ... 643 | - match: | 644 | class OutOfCheeseError(Exception): 645 | def __str__(self): 646 | ... 647 | - no-match: | 648 | class CheeseShopAddress: 649 | """The address of a cheese shop.""" 650 | - no-match: | 651 | class OutOfCheeseError(Exception): 652 | """No more cheese is available.""" 653 | def __str__(self): 654 | ... 655 | - no-match: | 656 | class _BrieCounter: 657 | limit: 500 658 | 659 | - id: docstrings-for-functions 660 | pattern: | 661 | def ${f}(...): 662 | """!!!""" 663 | ${body*} 664 | description: Functions should have docstrings 665 | explanation: | 666 | A function must have a docstring, unless it meets all of the following criteria: 667 | 668 | - not externally visible 669 | - very short 670 | - obvious 671 | 672 | From Google Style Guide [3.8.3](https://google.github.io/styleguide/pyguide.html#383-functions-and-methods) 673 | condition: not ((f.starts_with("_") and body.statement_count() < 5) or pattern.in_function_scope()) 674 | paths: 675 | exclude: 676 | - test_*.py 677 | - '*_test.py' 678 | tags: 679 | - google-python-style-guide 680 | - gpsg 681 | - gpsg-docstrings 682 | tests: 683 | - match: | 684 | def grow(plant): 685 | assert plant.is_alive() 686 | _grow(plant) 687 | - match: | 688 | def _grow(plant): 689 | pot = plant.owner.get_empty_pot() 690 | pot.contents.add("soil", fraction=0.5) 691 | pot.contents.add(plant, orientation="vertical") 692 | for day in range(0, plant.__class__.gestation_time, plant.__class__.watering_interval): 693 | pot.contents.add("water", amount="200ml") 694 | - no-match: | 695 | class PotContents: 696 | def add(item, fraction=None, amount=None, **kwargs): 697 | """Adds `item` to the pot. 698 | 699 | The pot can be filled to either a `fraction` of its volume, or by an `amount` specified. 700 | """ 701 | ... 702 | - no-match: | 703 | def _log_growth(plant): 704 | logger.info("plant size: %s", plant.size) 705 | - no-match: | 706 | def log_growth_error(f): 707 | """Captures and logs any growth errors inside `f`""" 708 | def wrapped(*args, **kwargs): 709 | try: 710 | return f(*args, **kwargs) 711 | except GrowthError: 712 | logger.warning("growth error") 713 | return wrapped 714 | 715 | - id: docstrings-for-packages 716 | pattern: | 717 | '''!!!''' 718 | ... 719 | condition: pattern.in_module_scope() 720 | paths: 721 | include: 722 | - __init__.py 723 | description: Packages should have docstrings 724 | explanation: | 725 | Python packages should contain a docstring describing the contents and usage of the package. 726 | 727 | From Google Style Guide [3.8.2](https://google.github.io/styleguide/pyguide.html#382-modules) 728 | tags: 729 | - google-python-style-guide 730 | - gpsg 731 | - gpsg-docstrings 732 | tests: 733 | - match: | 734 | def hello(): 735 | print("hello") 736 | - match: | 737 | class Hello: 738 | """Hello""" 739 | def hello(): 740 | """Prints 'hello'""" 741 | print("hello") 742 | - no-match: | 743 | """Hello module""" 744 | def hello(): 745 | print("hello") 746 | 747 | - id: docstrings-for-modules 748 | pattern: | 749 | '''!!!''' 750 | ... 751 | condition: pattern.in_module_scope() 752 | paths: 753 | exclude: 754 | - test_*.py 755 | - '*_test.py' 756 | - __init__.py 757 | description: Modules should have docstrings 758 | explanation: | 759 | Modules (Python files) should start with docstrings describing the contents and usage of the module. 760 | 761 | From Google Style Guide [3.8.2](https://google.github.io/styleguide/pyguide.html#382-modules) 762 | tags: 763 | - google-python-style-guide 764 | - gpsg 765 | - gpsg-docstrings 766 | tests: 767 | - match: | 768 | def hello(): 769 | print("hello") 770 | - match: | 771 | class Hello: 772 | """Hello""" 773 | def hello(): 774 | """Prints 'hello'""" 775 | print("hello") 776 | - no-match: | 777 | """Hello module""" 778 | def hello(): 779 | print("hello") 780 | 781 | # ---------------------------------------------------------------------------------- 782 | # 3.15 Getter and Setter Rules 783 | # ---------------------------------------------------------------------------------- 784 | # 785 | # The rule in this section prevents the definition of trivial getters and setters (i.e., 786 | # methods that have no extra logic apart from reading or writing an attribute). 787 | # 788 | # Individual examples for this rule in action can be found at 789 | # [examples/individual_rules/getter_and_setter_rules.py](./examples/individual_rules/getter_and_setter_rules.py) 790 | 791 | - id: avoid-trivial-properties 792 | description: Avoid defining trivial properties 793 | explanation: | 794 | Getter and setter functions (also called accessors and mutators) should be used when 795 | they provide a meaningful role or behavior for getting or setting a variable's 796 | value. 797 | 798 | In particular, they should be used when getting or setting the variable is complex 799 | or the cost is significant, either currently or in a reasonable future. 800 | 801 | If, for example, a pair of getters/setters simply read and write an internal 802 | attribute, the internal attribute should be made public instead. By comparison, if 803 | setting a variable means some state is invalidated or rebuilt, it should be a setter 804 | function. The function invocation hints that a potentially non-trivial operation is 805 | occurring. Alternatively, properties may be an option when simple logic is needed, 806 | or refactoring to no longer need getters and setters. 807 | 808 | From Google Style Guide [3.15](https://google.github.io/styleguide/pyguide#315-getters-and-setters) 809 | pattern: | 810 | @property 811 | def ${f}(self): 812 | return self.${value} 813 | 814 | ... 815 | 816 | @${f}.setter 817 | def ${setter}(self, ${other}): 818 | self.${value} = ${other} 819 | tags: 820 | - google-python-style-guide 821 | - gpsg 822 | tests: 823 | - match: | 824 | class Student: 825 | def __init__(self, name): 826 | self._name = name 827 | 828 | @property 829 | def name(self): 830 | return self._name 831 | 832 | @name.setter 833 | def name(self, new_name): 834 | self._name = new_name 835 | - match: | 836 | class Student: 837 | def __init__(self, name, grade): 838 | self._name = name 839 | self._grade = grade 840 | 841 | @property 842 | def name(self): 843 | return self._name 844 | 845 | def get_grade(self): 846 | return self._grade 847 | 848 | @name.setter 849 | def name(self, new_name): 850 | self._name = new_name 851 | - match: | 852 | class Student: 853 | def __init__(self, name: str) -> None: 854 | self._name: str = name 855 | 856 | @property 857 | def name(self) -> str: 858 | return self._name 859 | 860 | @name.setter 861 | def name(self, new_name: str) -> None: 862 | self._name = new_name 863 | - no-match: | 864 | class Student: 865 | def __init__(self, name: str) -> None: 866 | self._name: str = name 867 | 868 | @property 869 | def name(self) -> str: 870 | return self._name.title() # perform computation on name 871 | 872 | @name.setter 873 | def name(self, new_name: str) -> None: 874 | self._name = new_name 875 | - no-match: | 876 | class Student: 877 | def __init__(self, name: str) -> None: 878 | self._name: str = name 879 | 880 | @property 881 | def name(self) -> str: 882 | return self._name.title() # perform computation on name 883 | 884 | # no setter: this means that `Student.name` is read-only 885 | 886 | # ---------------------------------------------------------------------------------- 887 | # 3.16 Naming Rules 888 | # ---------------------------------------------------------------------------------- 889 | # 890 | # The rules in this section help ensure that variable, function and class names are 891 | # consistent, concise and descriptive. 892 | # 893 | # Individual examples for these rules in action can be found at 894 | # [examples/individual_rules/naming_rules.py](./examples/individual_rules/naming_rules.py) 895 | 896 | - id: avoid-single-character-names-variables 897 | pattern: ${var} = ${value} 898 | condition: var.character_count() == 1 899 | description: Avoid single character names 900 | explanation: | 901 | Avoid single character names, except for specifically allowed cases: 902 | 903 | - counters or iterators (e.g. `i`, `j`, `k`, `v`, et al.) 904 | - `e` as an exception identifier in `try`/`except` statements. 905 | - `f` as a file handle in `with` statements 906 | - private `TypeVar`s with no constraints (e.g. `_T`, `_U`, `_V`) 907 | 908 | From Google Style Guide [3.16.1](https://google.github.io/styleguide/pyguide.html#3161-names-to-avoid) 909 | tags: 910 | - google-python-style-guide 911 | - gpsg 912 | - gpsg-naming 913 | tests: 914 | - match: i = 0 915 | - no-match: initial_value = 0 916 | - match: A = get_account() 917 | - no-match: account = get_account() 918 | - no-match: | 919 | for i in range(10): 920 | print(i) 921 | - no-match: | 922 | try: 923 | explode_computer() 924 | except ExplosionError as e: 925 | pass 926 | - no-match: | 927 | with open("file.txt") as f: 928 | contents = f.read() 929 | - no-match: | 930 | from typing import TypeVar 931 | 932 | _T = TypeVar("_T") 933 | 934 | - id: avoid-single-character-names-functions 935 | pattern: | 936 | def ${function_name}(...): 937 | ... 938 | condition: function_name.character_count() == 1 939 | description: Avoid single character names 940 | explanation: | 941 | Avoid single character names, except for specifically allowed cases: 942 | 943 | - counters or iterators (e.g. `i`, `j`, `k`, `v`, et al.) 944 | - `e` as an exception identifier in `try`/`except` statements. 945 | - `f` as a file handle in `with` statements 946 | - private `TypeVar`s with no constraints (e.g. `_T`, `_U`, `_V`) 947 | 948 | From Google Style Guide [3.16.1](https://google.github.io/styleguide/pyguide.html#3161-names-to-avoid) 949 | tags: 950 | - google-python-style-guide 951 | - gpsg 952 | - gpsg-naming 953 | tests: 954 | - match: | 955 | def f(): 956 | pass 957 | - match: | 958 | async def f(): 959 | pass 960 | - no-match: | 961 | def function_with_good_and_descriptive_name(): 962 | pass 963 | - match: | 964 | def g(a, b: int = 0, **kwargs) -> str: 965 | if b > 5: 966 | return str(a) + str(b) + str(kwargs) 967 | return "b <= 4" 968 | - no-match: | 969 | def perform_calculation(a, b: int = 0, **kwargs) -> str: 970 | if b > 5: 971 | return str(a) + str(b) + str(kwargs) 972 | return "b <= 4" 973 | - match: | 974 | class MyClass: 975 | def m(self, a: int, b: float) -> str: 976 | print(a) 977 | return repr(b) 978 | - no-match: | 979 | class MyClass: 980 | def good_method(self, a: int, b: float) -> str: 981 | print(a) 982 | return repr(b) 983 | 984 | - id: name-type-suffix 985 | pattern: ${name} = ${value} 986 | condition: | 987 | name.ends_with("_dict") 988 | or name.ends_with("_list") 989 | or name.ends_with("_set") 990 | or name.ends_with("_int") 991 | or name.ends_with("_float") 992 | or name.ends_with("_str") 993 | description: Don't use the type of a variable as a suffix 994 | explanation: | 995 | Names shouldn't needlessly include the type of the variable. 996 | 997 | Such suffix might be OK for more complex types, 998 | e.g. first_account, advanced_account. 999 | But it's rarely necessary for built-in types. 1000 | 1001 | From Google Style Guide [3.16.1](https://google.github.io/styleguide/pyguide.html#s3.16-naming) 1002 | tags: 1003 | - google-python-style-guide 1004 | - gpsg 1005 | - gpsg-naming 1006 | tests: 1007 | - match: magic_int = 42 1008 | - match: magic_int = 42.00 1009 | - match: magic_float = 42 1010 | - match: magic_float = 42.00 1011 | - match: custom_notes_dict = {} 1012 | 1013 | - id: snake-case-variable-declarations 1014 | pattern: | 1015 | ${var}: ${type_annotation} 1016 | condition: not var.is_snake_case() and not var.in_module_scope() 1017 | description: Use snake case for variable names 1018 | explanation: | 1019 | Use snake case for variables. 1020 | 1021 | This rule catches only variables that were declared with a type annotation. 1022 | 1023 | From Google Style Guide [3.16.2](https://google.github.io/styleguide/pyguide.html#s3.16.2-naming-conventions) and [PEP 8](https://peps.python.org/pep-0008/#function-and-variable-names) 1024 | tags: 1025 | - google-python-style-guide 1026 | - gpsg 1027 | - gpsg-naming 1028 | - gpsg-naming-pep8 1029 | tests: 1030 | - match: | 1031 | def some_function(): 1032 | miXed: int 1033 | - match: | 1034 | def some_function(): 1035 | UpperCamelCase: int 1036 | - match: | 1037 | def some_function(): 1038 | UpperCamelCase42: int 1039 | - match: | 1040 | def some_function(): 1041 | mixed_and_underScore: int 1042 | - match: | 1043 | def some_function(): 1044 | too__many__underscores: str 1045 | - match: | 1046 | def some_function(): 1047 | _too__many__underscores: str 1048 | - match: | 1049 | def some_function(): 1050 | ___3_underscores_prefix: str 1051 | - match: | 1052 | def some_function(): 1053 | double_underscore_suffix__: str 1054 | - no-match: | 1055 | def some_function(): 1056 | nr: int = 42 1057 | - no-match: | 1058 | def some_function(): 1059 | miXed: int = 42 1060 | - no-match: | 1061 | def some_function(): 1062 | snake_nr: int = 42 1063 | - no-match: simple_NR = 42 1064 | - no-match: CONTEXT = "whatever" 1065 | - no-match: | 1066 | def some_function(): 1067 | _initial_value: int = 0 1068 | - no-match: | 1069 | def some_function(): 1070 | __initial_value: int = 0 1071 | - no-match: __version__ = "3.14" 1072 | 1073 | - id: snake-case-arguments 1074 | pattern: | 1075 | def ...(...,${arg_name}: ${type?} = ${default_value?},...): 1076 | ... 1077 | condition: (arg_name.is_dunder_name() or not arg_name.is_snake_case()) and not arg_name.equals("*") and not arg_name.equals("/") 1078 | description: Use snake case for arguments 1079 | explanation: | 1080 | Use snake case for function and method arguments. 1081 | 1082 | From Google Style Guide [3.16.2](https://google.github.io/styleguide/pyguide.html#s3.16.2-naming-conventions) and [PEP 8](https://peps.python.org/pep-0008/#function-and-method-arguments) 1083 | tags: 1084 | - google-python-style-guide 1085 | - gpsg 1086 | - gpsg-naming 1087 | - gpsg-naming-pep8 1088 | tests: 1089 | - match: | 1090 | def placeholder(miXed): 1091 | pass 1092 | - match: | 1093 | def placeholder(randomWord): 1094 | pass 1095 | - match: | 1096 | def placeholder(randomWord: str): 1097 | pass 1098 | - match: | 1099 | def placeholder(randomWord: str = "random"): 1100 | pass 1101 | - match: | 1102 | def placeholder(randomWord, other): 1103 | pass 1104 | - match: | 1105 | class Something: 1106 | def placeholder(self,myCamelWord): 1107 | pass 1108 | - match: | 1109 | def placeholder(too__many__underscores): 1110 | pass 1111 | - match: | 1112 | def placeholder(double_underscore_suffix__): 1113 | pass 1114 | - match: | 1115 | def placeholder(mixed_and_underScore): 1116 | pass 1117 | - match: | 1118 | def placeholder(__dunder_arg__): 1119 | pass 1120 | - no-match: | 1121 | def placeholder(nice_arg_name): 1122 | pass 1123 | - no-match: | 1124 | def placeholder(nice_arg_name: str): 1125 | pass 1126 | - no-match: | 1127 | def placeholder(nice_arg_name: str = "random"): 1128 | pass 1129 | - no-match: | 1130 | def placeholder(simple): 1131 | pass 1132 | - no-match: | 1133 | def placeholder(simple: bool = False): 1134 | pass 1135 | - no-match: | 1136 | def placeholder(nr, other_nr, sth_completely_different): 1137 | pass 1138 | - no-match: | 1139 | class Something: 1140 | def placeholder(self,simple: bool = False): 1141 | pass 1142 | 1143 | - id: snake-case-functions 1144 | pattern: | 1145 | def ${function_name}(...): 1146 | ... 1147 | condition: not function_name.is_snake_case() 1148 | description: Use snake case for function names 1149 | explanation: | 1150 | Use snake case for function and method names. 1151 | 1152 | From Google Style Guide [3.16.2](https://google.github.io/styleguide/pyguide.html#s3.16.2-naming-conventions) and [PEP 8](https://peps.python.org/pep-0008/#function-and-variable-names) 1153 | tags: 1154 | - google-python-style-guide 1155 | - gpsg 1156 | - gpsg-naming 1157 | - gpsg-naming-pep8 1158 | tests: 1159 | - match: | 1160 | def miXed(): 1161 | pass 1162 | - match: | 1163 | def UpperCamelCase(): 1164 | pass 1165 | - match: | 1166 | class Something: 1167 | def UpperCamelCase(self): 1168 | pass 1169 | - match: | 1170 | def too__many__underscores(): 1171 | pass 1172 | - match: | 1173 | def double_underscore_suffix__(): 1174 | pass 1175 | - match: | 1176 | def mixed_and_underScore(): 1177 | pass 1178 | - no-match: | 1179 | def nice_function_name(): 1180 | pass 1181 | - no-match: | 1182 | def _private(): 1183 | pass 1184 | - no-match: | 1185 | def __very_private(): 1186 | pass 1187 | - no-match: | 1188 | class Something: 1189 | def single(self): 1190 | pass 1191 | - no-match: | 1192 | class Something: 1193 | def __init__(self): 1194 | pass 1195 | 1196 | - id: upper-camel-case-classes 1197 | pattern: | 1198 | class ${class_name}(...): 1199 | ... 1200 | condition: not class_name.is_upper_camel_case() 1201 | description: Use upper camel case for class names 1202 | explanation: | 1203 | Use upper camel case for class names. 1204 | 1205 | From Google Style Guide [3.16.2](https://google.github.io/styleguide/pyguide.html#s3.16.2-naming-conventions) and [PEP 8](https://peps.python.org/pep-0008/#class-names) 1206 | tags: 1207 | - google-python-style-guide 1208 | - gpsg 1209 | - gpsg-naming 1210 | - gpsg-naming-pep8 1211 | tests: 1212 | - match: | 1213 | class lower: 1214 | pass 1215 | - match: | 1216 | class UPPER: 1217 | pass 1218 | - match: | 1219 | class snake_case: 1220 | pass 1221 | - match: | 1222 | class UPPER_UNDERSCORE: 1223 | pass 1224 | - match: | 1225 | class UpperCamelCase_WithUnderscore: 1226 | pass 1227 | - no-match: | 1228 | class UpperCamelCase: 1229 | pass 1230 | - no-match: | 1231 | class UpperCamelCase42: 1232 | pass 1233 | - no-match: | 1234 | class B: 1235 | pass 1236 | - no-match: | 1237 | class _PrivateUpperCamelCase123: 1238 | pass 1239 | 1240 | # ---------------------------------------------------------------------------------- 1241 | # 3.18 Function Length Rules 1242 | # ---------------------------------------------------------------------------------- 1243 | # 1244 | # The rule in this section ensures that functions and methods are short. 1245 | # 1246 | # Individual examples for this rule in action can be found at 1247 | # [examples/individual_rules/function_length_rules.py](./examples/individual_rules/function_length_rules.py) 1248 | 1249 | - id: no-long-functions 1250 | pattern: | 1251 | def ${name}(...): 1252 | ${statements+} 1253 | condition: statements.statement_count() > 40 1254 | description: Functions should be less than 40 lines 1255 | explanation: | 1256 | Prefer small and focused functions. 1257 | 1258 | We recognize that long functions are sometimes appropriate, so no hard limit is placed on function length. If a function exceeds about 40 lines, think about whether it can be broken up without harming the structure of the program. 1259 | 1260 | Even if your long function works perfectly now, someone modifying it in a few months may add new behavior. This could result in bugs that are hard to find. Keeping your functions short and simple makes it easier for other people to read and modify your code. 1261 | 1262 | From Google Style Guide [3.18](https://google.github.io/styleguide/pyguide.html#318-function-length) 1263 | tags: 1264 | - google-python-style-guide 1265 | - gpsg 1266 | tests: 1267 | - no-match: | 1268 | def f(a, b) -> int: 1269 | x() 1270 | 1271 | rule_tags: 1272 | gpsg-naming-snake-case: 1273 | - snake-case-functions 1274 | - snake-case-arguments 1275 | - snake-case-variable-declarations 1276 | -------------------------------------------------------------------------------- /remove-debugging-statements.yaml: -------------------------------------------------------------------------------- 1 | # These optional rules flag or remove various debugging statements. 2 | # 3 | # All rules in this file have the tags: 4 | # 5 | # * `remove-debugging-statements` 6 | # * `no-debug`. 7 | # 8 | 9 | rules: 10 | - id: remove-breakpoint 11 | description: Remove breakpoints from production code 12 | explanation: Breakpoints shouldn't be used in production code. 13 | pattern: breakpoint(...) 14 | replacement: '' 15 | tests: 16 | - match: breakpoint() 17 | - match: breakpoint("sth") 18 | tags: 19 | - remove-debugging-statements 20 | - no-debug 21 | 22 | - id: remove-pdb-set_trace 23 | description: Remove `pdb.set_trace` calls from production code 24 | explanation: Breakpoints shouldn't be used in production code. 25 | pattern: pdb.set_trace(...) 26 | replacement: '' 27 | tests: 28 | - match: | 29 | import pdb 30 | pdb.set_trace() 31 | - match: | 32 | import pdb as pdb_debug 33 | pdb_debug.set_trace() 34 | tags: 35 | - remove-debugging-statements 36 | - no-debug 37 | 38 | - id: flag-print 39 | description: Remove `print` statements from production code 40 | explanation: | 41 | `print` statements shouldn't be used in production code. 42 | Use: 43 | 44 | * a CLI framework for output 45 | * `logger.debug()` for debugging information. 46 | pattern: print(...) 47 | tests: 48 | - match: print("sth") 49 | - match: print(x, y) 50 | - no-match: custom_print("sth") 51 | tags: 52 | - remove-debugging-statements 53 | - no-debug 54 | 55 | - id: flag-streamlit-show 56 | description: Don't use Streamlit's `experimental_show` in production code 57 | explanation: | 58 | `st.experimental_show` should be used only for debugging purposes. See the [Streamlit docs](https://docs.streamlit.io/library/api-reference/utilities/st.experimental_show) Use `st.write()` in production 59 | pattern: streamlit.experimental_show(...) 60 | replacement: '' 61 | tests: 62 | - match: | 63 | import streamlit as st 64 | st.experimental_show(df) 65 | - match: | 66 | import streamlit as st 67 | def some_function(): 68 | st.experimental_show(df) 69 | - match: | 70 | import streamlit 71 | streamlit.experimental_show(df) 72 | - no-match: | 73 | import streamlit as st 74 | st.snow(df) 75 | - match: | 76 | from streamlit import experimental_show 77 | experimental_show(something) 78 | - match: | 79 | from streamlit import experimental_show as exp_show 80 | exp_show(something) 81 | - no-match: other_package.experimental_show() 82 | - no-match: | 83 | import st_other_package as st 84 | def some_function(): 85 | st.experimental_show(df) 86 | tags: 87 | - remove-debugging-statements 88 | - no-debug 89 | - streamlit 90 | -------------------------------------------------------------------------------- /use-fstring.yaml: -------------------------------------------------------------------------------- 1 | # This file contains optional rules 2 | # that spot when f-string should be used 3 | # instead of an older string formatting mechanism. 4 | # 5 | # Related default rules: 6 | # 7 | # https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-concatenation/ 8 | # https://docs.sourcery.ai/Reference/Default-Rules/refactorings/use-fstring-for-formatting/ 9 | 10 | rules: 11 | # You can use this rule in the Sourcery CLI with the --enable option: 12 | # 13 | # sourcery review --enable no-str-format . 14 | # 15 | # To review with this rule and the related default f-string rules: 16 | # 17 | # sourcery review --enable use-fstring . 18 | # 19 | - id: no-str-format 20 | description: Use f-string instead of `str.format` 21 | explanation: | 22 | f-string is less verbose and faster than `str.format()` 23 | 24 | f-string was introduced to use the same syntax and machinery as `str.format()`, but solve some of its issues: 25 | 26 | * Verbosity. 27 | * The placeholder and the replacement value are often far away from each other. 28 | 29 | See the [Rationale section of PEP-498](https://peps.python.org/pep-0498/#rationale). 30 | 31 | pattern: ${text}.format(...) 32 | condition: text.has_type("str") 33 | tests: 34 | - match: "'{0}. place'.format(placement)" 35 | - match: "'{0}. chapter {1}'.format(nr, title)" 36 | - match: | 37 | template = "{0}. place" 38 | template.format(placement) 39 | - no-match: | 40 | sth = CustomObject() 41 | sth.format(placement) 42 | 43 | rule_tags: 44 | use-fstring: 45 | - no-str-format 46 | - use-fstring-for-concatenation 47 | - use-fstring-for-formatting 48 | --------------------------------------------------------------------------------