# Static analysis for Python code (ie: ruff)

**URL:** <https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697>\
**Category:** Developers\
**Created:** [July 16, 2024, 3:22pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697 "2024-07-16T15:22:35Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![mateidibu](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@mateidibu](https://klipper.discourse.group/u/mateidibu)\
**Post date:** [July 16, 2024, 3:22pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/1 "2024-07-16T15:22:35Z")

</div>

As far as I can tell, there isn’t any configuration for a static analysis tool for the python code. Tools like [ruff](https://github.com/astral-sh/ruff) detect a lot of issues in `klippy/`, some of them irrelevant and some potentially dangerous.

I’m willing to be the main driving force and contributor, following a plan similar to this:

1. Derive a list of unique issue codes when running ruff with the default config
2. Decide which warnings/errors are worthwhile and which are irrelevant
3. Open a PR for each worthwhile warning/error
4. Derive a list of unique issue codes when running ruff with `--include ALL`
5. repeat step 2
6. repeat step 3
7. write a `ruff.toml` which ignores the irrelevant rules discussed in steps 1 and 4
8. integrate ruff as a CI step/github action/pre-commit hook

---

<div class="post-metadata">

**Author:** ![mykepredko](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/mykepredko/32/2085_2.png) [@mykepredko](https://klipper.discourse.group/u/mykepredko)\
**Post date:** [July 16, 2024, 3:54pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/2 "2024-07-16T15:54:30Z")

</div>

I’m happy to assist in this as well.

---

<div class="post-metadata">

**Author:** ![koconnor](https://avatars.discourse-cdn.com/v4/letter/k/aca169/32.png) [@koconnor](https://klipper.discourse.group/u/koconnor)\
**Post date:** [July 20, 2024, 6:47pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/3 "2024-07-20T18:47:26Z")

</div>

Thanks. I’m not familiar with that tool. I guess the main question I have is - what kinds of errors does it find on the current code?

If it can reliably find “real” errors than I think it could have value adding it to Klipper’s regression test suite. (By “errors” I mean catching real-world faults that could occur during run-time.) We’d then need to evaluate the balance between false-positives and actual found errors.

However, if it reports on indentation or “style issues” than I’m not, personally, interested in it. As, in my experience, chasing a particular coding style leads to unnecessary code churn that complicates merges and can introduce subtle errors.

Cheers,  
-Kevin

---

<div class="post-metadata">

**Author:** ![mateidibu](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@mateidibu](https://klipper.discourse.group/u/mateidibu)\
**Post date:** [July 21, 2024, 8:19am UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/4 "2024-07-21T08:19:26Z")

</div>

I’ve opened a PR for the `roff.toml` config file at [[WIP] ruff: init by matdibu · Pull Request #6651 · Klipper3d/klipper · GitHub](https://github.com/Klipper3d/klipper/pull/6651)

---

<div class="post-metadata">

**Author:** ![Sineos](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sineos/32/18_2.png) [@Sineos](https://klipper.discourse.group/u/Sineos)\
**Post date:** [July 21, 2024, 9:19am UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/5 "2024-07-21T09:19:05Z")

</div>

If I understood Kevin correctly, the real question is, whether the majority of identified issues are “stylistic noise” or real errors that would need attention.

> **[Klipper\_ruff](https://docs.google.com/spreadsheets/d/1jW_u0KhUKtVgkimLR_YRLF_Ve8V_nzyUJ32HjZEqrTU/edit?usp=sharing)**
>
> Tabelle2
> 
> Error,Count
> E401 \[\*\] Multiple imports on one line,104
> E722 Do not use bare \`except\`,63
> F841 \[\*\] Local variable \`e\` is assigned to but never used,38
> F401 \[\*\] \`logging\` imported but unused,30
> E741 Ambiguous variable name,29
> E703 \[\*\]...

This is the current code base and around 30% are “Multiple imports on one line”.  
What are real issues and what might just be “sloppiness” or unneeded is beyond me to judge.

Edit:  
If you want to drill down even further, it results in:

 ![grafik](https://global.discourse-cdn.com/free1/uploads/klipper/original/2X/e/ec4350d73ed63e9bcb2592d0fb36614008478363.png)

Edit 2:  
Nice command line options. Even offers statistics. Should have checked before.  
The entire glory:

```auto
tklipper@bigtreetech-cb1 ~/klipper $ ruff check --select ALL --statistics
warning: `one-blank-line-before-class` (D203) and `no-blank-line-before-class` (D211) are incompatible. Ignoring `one-blank-line-before-class`.
warning: `multi-line-summary-first-line` (D212) and `multi-line-summary-second-line` (D213) are incompatible. Ignoring `multi-line-summary-second-line`.
5139 Q000 [*] bad-quotes-inline-string
4254 ANN001 [] missing-type-function-argument
2348 ANN201 [] missing-return-type-undocumented-public-function
1851 D102 [] undocumented-public-method
 803 UP031 [*] printf-string-formatting
 767 ANN202 [] missing-return-type-private-function
 433 D103 [] undocumented-public-function
 407 D101 [] undocumented-public-class
 397 ANN204 [] missing-return-type-special-method
 376 D107 [] undocumented-public-init
 263 COM812 [*] missing-trailing-comma
 254 PLR2004 [] magic-value-comparison
 247 ARG002 [] unused-method-argument
 247 N802 [] invalid-function-name
 242 TRY003 [] raise-vanilla-args
 240 EM101 [*] raw-string-in-exception
 188 D100 [] undocumented-public-module
 187 N806 [] non-lowercase-variable-in-function
 155 I001 [*] unsorted-imports
 153 UP032 [*] f-string
 126 N815 [] mixed-case-variable-in-class-scope
 124 FBT002 [] boolean-default-value-positional-argument
 115 FBT003 [] boolean-positional-value-in-call
 104 E401 [*] multiple-imports-on-one-line
  89 D212 [*] multi-line-summary-first-line
  82 G002 [] logging-percent-format
  74 PLW0603 [] global-statement
  69 SLF001 [] private-member-access
  66 B904 [] raise-without-from-inside-except
  64 PTH123 [] builtin-open
  63 E722 [] bare-except
  59 N801 [] invalid-class-name
  56 C901 [] complex-structure
  56 F841 [*] unused-variable
  53 T201 [*] print
  53 D205 [] blank-line-after-summary
  51 ERA001 [*] commented-out-code
  47 PTH118 [] os-path-join
  47 F401 [] unused-import
  44 PLW2901 [] redefined-loop-name
  43 B007 [] unused-loop-control-variable
  42 PLR0912 [] too-many-branches
  40 UP024 [*] os-error-alias
  37 SIM115 [] open-file-with-context-handler
  34 D401 [] non-imperative-mood
  30 D200 [*] fits-on-one-line
  29 E741 [] ambiguous-variable-name
  29 RUF005 [*] collection-literal-concatenation
  26 RET505 [] superfluous-else-return
  26 PTH120 [] os-path-dirname
  24 SIM105 [] suppressible-exception
  24 PLR0913 [] too-many-arguments
  23 PLR0915 [] too-many-statements
  22 INP001 [] implicit-namespace-package
  22 RET503 [*] implicit-return
  21 RET504 [*] unnecessary-assign
  21 ARG001 [] unused-function-argument
  21 PTH110 [] os-path-exists
  20 SIM102 [] collapsible-if
  20 SIM208 [*] double-negation
  19 ICN001 [] unconventional-import-alias
  19 D400 [] ends-in-period
  19 D415 [] ends-in-punctuation
  18 TD002 [] missing-todo-author
  18 TD003 [] missing-todo-link
  18 PD901 [] pandas-df-variable-name
  18 UP015 [*] redundant-open-modes
  17 ANN002 [] missing-type-args
  17 ISC002 [] multi-line-implicit-string-concatenation
  17 RUF012 [] mutable-class-default
  16 ANN003 [] missing-type-kwargs
  16 BLE001 [] blind-except
  15 SIM108 [] if-else-block-instead-of-if-exp
  15 E703 [*] useless-semicolon
  15 UP008 [*] super-call-with-parameters
  14 S603 [] subprocess-without-shell-equals-true
  14 COM819 [*] prohibited-trailing-comma
  13 B006 [*] mutable-argument-default
  13 TRY002 [] raise-vanilla-class
  12 ARG005 [] unused-lambda-argument
  11 FIX003 [] line-contains-xxx
  11 TD001 [] invalid-todo-tag
  11 TD004 [] missing-todo-colon
  11 N818 [] error-suffix-on-exception-name
  11 E721 [] type-comparison
  11 D105 [] undocumented-magic-method
  11 UP034 [*] extraneous-parentheses
  10 EM103 [*] dot-format-in-exception
  10 PIE810 [*] multiple-starts-ends-with
   9 S108 [] hardcoded-temp-file
   9 S110 [] try-except-pass
   9 ISC003 [] explicit-string-concatenation
   9 PERF203 [] try-except-in-loop
   9 E402 [] module-import-not-at-top-of-file
   9 UP004 [*] useless-object-inheritance
   8 N803 [] invalid-argument-name
   8 PERF401 [] manual-list-comprehension
   8 E731 [*] lambda-assignment
   8 F821 [] undefined-name
   7 A001 [] builtin-variable-shadowing
   7 FIX002 [] line-contains-todo
   7 G004 [] logging-f-string
   7 PTH111 [] os-path-expanduser
   7 E701 [] multiple-statements-on-one-line-colon
   7 UP007 [*] non-pep604-annotation
   6 S324 [] hashlib-insecure-hash-function
   6 A002 [] builtin-argument-shadowing
   6 G003 [] logging-string-concat
   6 SIM223 [*] expr-and-false
   6 PTH100 [] os-path-abspath
   6 PTH108 [] os-unlink
   6 E501 [] line-too-long
   6 E711 [*] none-comparison
   6 D204 [*] one-blank-line-after-class
   6 D300 [*] triple-single-quotes
   6 PLR5501 [*] collapsible-else-if
   6 UP027 [*] unpacked-list-comprehension
   6 TRY300 [] try-consider-else
   6 TRY400 [*] error-instead-of-exception
   5 DTZ004 [] call-datetime-utcfromtimestamp
   5 EXE001 [] shebang-not-executable
   5 PYI024 [] collections-named-tuple
   5 Q002 [*] bad-quotes-docstring
   5 RET506 [] superfluous-else-raise
   5 SIM118 [*] in-dict-keys
   5 TD005 [] missing-todo-description
   5 PTH119 [] os-path-basename
   5 PLR0911 [] too-many-return-statements
   4 S605 [] start-process-with-a-shell
   4 C419 [*] unnecessary-comprehension-in-call
   4 PIE808 [*] unnecessary-range-start
   4 RET502 [*] implicit-return-value
   4 SIM113 [] enumerate-for-loop
   4 PTH202 [] os-path-getsize
   4 PD011 [] pandas-use-of-dot-values
   4 D104 [] undocumented-public-package
   4 UP006 [*] non-pep585-annotation
   3 ANN206 [] missing-return-type-class-method
   3 C413 [*] unnecessary-call-around-sorted
   3 C416 [*] unnecessary-comprehension
   3 SIM210 [*] if-expr-with-true-false
   3 SIM300 [*] yoda-conditions
   3 TID252 [*] relative-imports
   3 PTH113 [] os-path-isfile
   3 D106 [] undocumented-public-nested-class
   3 PLR1704 [] redefined-argument-from-local
   3 PLW0602 [] global-variable-not-assigned
   3 UP009 [*] utf8-encoding-declaration
   3 UP010 [*] unnecessary-future-import
   3 TRY301 [] raise-within-try
   2 EM102 [*] f-string-in-exception
   2 FIX004 [] line-contains-hack
   2 SIM103 [*] needless-bool
   2 SIM910 [*] dict-get-with-none-default
   2 PTH104 [] os-rename
   2 PTH122 [] os-path-splitext
   2 PTH204 [] os-path-getmtime
   2 PTH207 [] glob
   2 W605 [*] invalid-escape-sequence
   2 D402 [] no-signature
   2 D407 [*] dashed-underline-after-section
   2 D413 [*] blank-line-after-last-section
   2 PLE1307 [] bad-string-format-type
   2 PLR1714 [*] repeated-equality-comparison
   2 PLR1722 [*] sys-exit-alias
   2 UP003 [*] type-of-primitive
   2 RUF015 [*] unnecessary-iterable-allocation-for-first-element
   1 [] syntax-error
   1 ASYNC230 [] blocking-open-call-in-async-function
   1 S101 [] assert
   1 S307 [] suspicious-eval-usage
   1 S602 [] subprocess-popen-with-shell-equals-true
   1 S701 [] jinja2-autoescape-false
   1 B018 [] useless-expression
   1 C408 [*] unnecessary-collection-call
   1 RSE102 [*] unnecessary-paren-on-raise-exception
   1 RET507 [] superfluous-else-continue
   1 SIM101 [*] duplicate-isinstance-call
   1 SIM112 [] uncapitalized-environment-variables
   1 SIM114 [*] if-with-same-arms
   1 SIM117 [] multiple-with-statements
   1 TD006 [*] invalid-todo-capitalization
   1 PTH101 [] os-chmod
   1 PTH102 [] os-mkdir
   1 PTH103 [] os-makedirs
   1 PTH107 [] os-remove
   1 PTH109 [] os-getcwd
   1 PTH114 [] os-path-islink
   1 PTH116 [] os-stat
   1 N999 [] invalid-module-name
   1 PERF102 [*] incorrect-dict-iterator
   1 PERF402 [] manual-list-copy
   1 E713 [*] not-in-test
   1 D405 [*] capitalize-section-name
   1 D416 [*] section-name-ends-in-colon
   1 F402 [] import-shadowed-by-loop-var
   1 F811 [] redefined-while-unused
   1 PLR0402 [*] manual-from-import
   1 PLR1711 [*] useless-return
   1 UP020 [*] open-alias
   1 FURB136 [*] if-expr-min-max
   1 RUF006 [] asyncio-dangling-task
   1 TRY201 [*] verbose-raise
   1 TRY401 [] verbose-log-message
tklipper@bigtreetech-cb1 ~/klipper $

```

---

<div class="post-metadata">

**Author:** ![mateidibu](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@mateidibu](https://klipper.discourse.group/u/mateidibu)\
**Post date:** [July 21, 2024, 2:44pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/6 "2024-07-21T14:44:11Z")

</div>

I’m aware that not all “errors” (and especially not all “errors” reported by `--select ALL`) are useful.

I fully agree that things like “multiple imports on one line” can be safely ignored and added to the config file’s `ignore` list.

My intention was to start this process without using `--select ALL` specifically to make the introduction easier. And I’m not proposing the sudden introduction of ruff as a blocking step in CI.

Dropping a “ruff.toml” in the project’s root will have no effect on those that don’t explicitly run it or have it in their editor configuration.

---

<div class="post-metadata">

**Author:** ![mateidibu](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@mateidibu](https://klipper.discourse.group/u/mateidibu)\
**Post date:** [July 21, 2024, 2:58pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/7 "2024-07-21T14:58:11Z")

</div>

That being said, should the discussion about the specific error codes to fix/ignore be hosted here or on the pull request ?

---

<div class="post-metadata">

**Author:** ![mateidibu](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@mateidibu](https://klipper.discourse.group/u/mateidibu)\
**Post date:** [July 21, 2024, 3:00pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/8 "2024-07-21T15:00:37Z")

</div>

But back to the basics, this, as a whole, is a python 3 project, right? Is it a specific version of python 3? It should be included in the config as well.

---

<div class="post-metadata">

**Author:** ![Sineos](https://yyz2.discourse-cdn.com/free1/user_avatar/klipper.discourse.group/sineos/32/18_2.png) [@Sineos](https://klipper.discourse.group/u/Sineos)\
**Post date:** [July 21, 2024, 3:49pm UTC](https://klipper.discourse.group/t/static-analysis-for-python-code-ie-ruff/17697/9 "2024-07-21T15:49:29Z")

</div>

> [@mateidibu](#):
>
> My intention was to start this process without using `--select ALL` specifically to make the introduction easier.

The shared spreadsheet and the graph above have just been run without any command line options.  
I just played around with it out of pure interest.

> [@mateidibu](#):
>
> But back to the basics, this, as a whole, is a python 3 project, right? Is it a specific version of python 3? It should be included in the config as well.

As far as my understanding goes, Klipper currently is still maintaining Python 2 backwards compatibility, as many installations and even the default installation procedure in the documentation is still based on Python 2.

But for a final call and for the rest of your questions, @koconnor is the relevant authority to answer them.
