Skip to content

Commit

Permalink
Add ruff rules and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexHls committed Nov 27, 2023
1 parent 8e0b349 commit ecd349e
Show file tree
Hide file tree
Showing 3 changed files with 327 additions and 0 deletions.
230 changes: 230 additions & 0 deletions .ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# Ruff configuration file adapted from astropy
extend = "pyproject.toml"
lint.ignore = [
# NOTE: to find a good code to fix, run:
# ruff --select="ALL" --statistics astropy/<subpackage>

# flake8-annotations (ANN) : static typing
"ANN001", # Function argument without type annotation
"ANN003", # `**kwargs` without type annotation
"ANN201", # Public function without return type annotation
"ANN202", # Private function without return type annotation
"ANN401", # Use of `Any` type

# flake8-unused-arguments (ARG)
"ARG001", # unused-function-argument
"ARG002", # unused-method-argument
"ARG003", # unused-class-method-argument
"ARG004", # unused-static-method-argument
"ARG005", # unused-lambda-argument

# flake8-bugbear (B)
"B006", # MutableArgumentDefault
"B007", # UnusedLoopControlVariable
"B023", # FunctionUsesLoopVariable
"B028", # No-explicit-stacklevel
"B904", # RaiseWithoutFromInsideExcept
"B905", # ZipWithoutExplicitStrict # requires 3.10+

# flake8-blind-except (BLE)
"BLE001", # blind-except

# mccabe (C90) : code complexity
# TODO: configure maximum allowed complexity.
"C901", # McCabeComplexity

# pydocstyle (D)
# Missing Docstrings
"D100", # undocumented-public-module
"D101", # undocumented-public-class
"D103", # undocumented-public-function
"D104", # undocumented-public-package
"D205", # blank-line-after-summary
# Quotes Issues
"D300", # triple-single-quotes
"D301", # escape-sequence-in-docstring
# Docstring Content Issues
"D403", # first-line-capitalized
"D404", # docstring-starts-with-this
"D401", # non-imperative-mood.
"D414", # empty-docstring-section
"D419", # docstring is empty

# flake8-datetimez (DTZ)
"DTZ001", # call-datetime-without-tzinfo
"DTZ005", # call-datetime-now-without-tzinfo

# pycodestyle (E, W)
"E501", # line-too-long
"E721", # type-comparison
"E731", # lambda-assignment

# flake8-errmsg (EM) : nicer error tracebacks
"EM101", # raw-string-in-exception
"EM102", # f-string-in-exception
"EM103", # dot-format-in-exception

# eradicate (ERA)
# NOTE: be careful that developer notes are kept.
"ERA001", # commented-out-code

# flake8-executable (EXE)
"EXE002", # shebang-missing-executable-file

# Pyflakes (F)
"F841", # unused-variable

# flake8-boolean-trap (FBT) : boolean flags should be kwargs, not args
# NOTE: a good thing to fix, but changes API.
"FBT001", # boolean-positional-arg-in-function-definition
"FBT002", # boolean-default-value-in-function-definition
"FBT003", # boolean-positional-value-in-function-call

# flake8-fixme (FIX)
"FIX001", # Line contains FIXME. this should be fixed or at least FIXME replaced with TODO
"FIX004", # Line contains HACK. replace HACK with NOTE.

# flake8-import-conventions (ICN) : use conventional import aliases
"ICN001", # import-conventions

# pep8-naming (N)
# NOTE: some of these can/should be fixed, but this changes the API.
"N801", # invalid-class-name
"N802", # invalid-function-name
"N803", # invalid-argument-name
"N804", # invalid-first-argument-name-for-class-method
"N805", # invalid-first-argument-name-for-method
"N807", # dunder-function-name
"N813", # camelcase-imported-as-lowercase
"N815", # mixed-case-variable-in-class-scope
"N816", # mixed-case-variable-in-global-scope
"N818", # error-suffix-on-exception-name

# NumPy-specific rules (NPY)
"NPY002", # Replace legacy `np.random.rand` call with `np.random.Generator` (2023-05-03)

# Perflint (PERF)
"PERF203", # `try`-`except` within a loop incurs performance overhead
"PERF401", # Use a list comprehension to create a transformed list

# pygrep-hooks (PGH)
"PGH001", # eval

# Pylint (PLC, PLE, PLR, PLW)
"PLE0101", # return-in-init
"PLR0124", # Name compared with itself
"PLR0402", # ConsiderUsingFromImport
"PLR0911", # too-many-return-statements
"PLR0912", # too-many-branches
"PLR0913", # too-many-args
"PLR0915", # too-many-statements
"PLR1714", # Consider merging multiple comparisons
"PLR2004", # MagicValueComparison
"PLR5501", # collapsible-else-if
"PLW0603", # global-statement
"PLW2901", # redefined-loop-name

# flake8-pytest-style (PT)
"PT001", # pytest-fixture-incorrect-parentheses-style
"PT003", # pytest-extraneous-scope-function
"PT004", # pytest-missing-fixture-name-underscore
"PT006", # pytest-parametrize-names-wrong-type
"PT007", # pytest-parametrize-values-wrong-type
"PT011", # pytest-raises-too-broad
"PT012", # pytest-raises-with-multiple-statements
"PT017", # pytest-assert-in-exceptinstead
"PT018", # pytest-composite-assertion
"PT022", # pytest-useless-yield-fixture
"PT023", # pytest-incorrect-mark-parentheses-style

# flake8-use-pathlib (PTH)
"PTH100", # os-path-abspath
"PTH101", # os-chmod
"PTH102", # os-mkdir
"PTH103", # os-makedirs
"PTH104", # os-rename
"PTH107", # os-remove
"PTH108", # os-unlink
"PTH109", # os-getcwd
"PTH110", # os-path-exists
"PTH111", # os-path-expanduser
"PTH112", # os-path-isdir
"PTH113", # os-path-isfile
"PTH118", # os-path-join
"PTH119", # os-path-basename
"PTH120", # os-path-dirname
"PTH122", # os-path-splitext
"PTH123", # builtin-open
"PTH202", # `os.path.getsize` should be replaced by `Path.stat().st_size`
"PTH207", # Replace `glob` with `Path.glob` or `Path.rglob`

# flake8-return (RET)
"RET501", # unnecessary-return-none
"RET502", # implicit-return-value
"RET503", # implicit-return
"RET504", # unnecessary-assign
"RET505", # superfluous-else-return
"RET506", # superfluous-else-raise
"RET507", # superfluous-else-continue

# flake8-raise (RSE)
"RSE102", # unnecessary-paren-on-raise-exception

# Ruff-specific rules (RUF)
"RUF001", # ambiguous-unicode-character-string
"RUF002", # ambiguous-unicode-character-docstring
"RUF010", # use conversion in f-string
"RUF012", # Mutable class attributes should be annotated with `typing.ClassVar`

# flake8-bandit (S)
"S101", # Use of `assert` detected
"S105", # hardcoded-password-string
"S110", # try-except-pass
"S112", # try-except-continue
"S301", # suspicious-pickle-usage
"S307", # Use of possibly insecure function; consider using `ast.literal_eval`
"S311", # suspicious-non-cryptographic-randomness
"S324", # hashlib-insecure-hash-function
"S506", # UnsafeYAMLLoad
"S310", # Suspicious-url-open-usage
"S603", # `subprocess` call: check for execution of untrusted input
"S607", # Starting a process with a partial executable path

# flake8-simplify (SIM)
"SIM102", # NestedIfStatements
"SIM105", # UseContextlibSuppress
"SIM108", # UseTernaryOperator
"SIM114", # if-with-same-arms
"SIM115", # OpenFileWithContextHandler
"SIM117", # MultipleWithStatements
"SIM118", # KeyInDict
"SIM201", # NegateEqualOp
"SIM300", # yoda condition

# flake8-print (T20)
"T201", # PrintUsed

# flake8-todos (TD)
"TD001", # Invalid TODO tag
"TD003", # Missing issue link on the line following this TODO
"TD004", # Missing colon in TODO
"TD007", # Missing space after colon in TODO

# tryceratops (TRY)
"TRY002", # raise-vanilla-class
"TRY003", # raise-vanilla-args
"TRY004", # prefer-type-error
"TRY200", # reraise-no-cause
"TRY201", # verbose-raise
"TRY300", # Consider `else` block
"TRY301", # raise-within-try

# flake8-quotes (Q)
"Q000", # use double quotes
]
lint.unfixable = [
"E711" # NoneComparison. Hard to fix b/c numpy has it's own None.
]

[lint.extend-per-file-ignores]
"docs/*" = []
17 changes: 17 additions & 0 deletions docs/contributing/development/code_quality.rst
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,23 @@ A better method is to run Black automatically - first `integrate it within the c

.. warning :: If your code doesn't follow the Black code style, then the Black-check action on your PR will fail.
Ruff
----
`Ruff <https://docs.astral.sh/ruff/>`_ is a code linter and formatter that checks for common mistakes and automatically fixes them. It is currently not installed in the TARDIS conda environment, so you will have to install it manually: ::

conda install -c conda-forge ruff

To run Ruff, use the following command: ::

ruff check <source_file_or_directory> # Lints the code
ruff check <source_file_or_directory> --fix # Lints and fixes any fixable errors

Currently, Ruff is not integrated with the TARDIS CI and is not a requirement for merging a PR. However, it is recommended to run Ruff on your code before commiting it to ensure that new code already follows these rules.

.. note :: We adopt the linting rules utilized by astropy. Permanent rules are defined in the ``pyproject.toml``, non-permanent rules are defined in the ``.ruff.toml`` file. If you want to add a new rule, please add it to the ``.ruff.toml`` file. If you want to add a permanent rule, please open a PR to the ``pyproject.toml``.
.. note :: Ruff can also be used for formatting code, but for now we recommend using Black for this purpose as the CI is configured to run Black on all PRs.
Naming Conventions
------------------

Expand Down
80 changes: 80 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,83 @@ omit-covered-files = false
quiet = false
verbose = 0
#whitelist-regex = []

[tool.ruff]
lint.select = ["ALL"]
exclude=[]
lint.ignore = [ # NOTE: non-permanent exclusions should be added to `.ruff.toml` instead.

# flake8-builtins (A) : shadowing a Python built-in.
# New ones should be avoided and is up to maintainers to enforce.
"A00",

# flake8-annotations (ANN)
"ANN101", # No annotation for `self`.
"ANN102", # No annotation for `cls`.

# flake8-bugbear (B)
"B008", # FunctionCallArgumentDefault

# flake8-commas (COM)
"COM812", # TrailingCommaMissing
"COM819", # TrailingCommaProhibited

# pydocstyle (D)
# Missing Docstrings
"D102", # Missing docstring in public method. Don't check b/c docstring inheritance.
"D105", # Missing docstring in magic method. Don't check b/c class docstring.
# Whitespace Issues
"D200", # FitsOnOneLine
# Docstring Content Issues
"D410", # BlankLineAfterSection. Using D412 instead.
"D400", # EndsInPeriod. NOTE: might want to revisit this.

# pycodestyle (E, W)
"E711", # NoneComparison (see unfixable)
"E741", # AmbiguousVariableName. Physics variables are often poor code variables

# flake8-fixme (FIX)
"FIX002", # Line contains TODO | notes for improvements are OK iff the code works

# pep8-naming (N)
"N803", # invalid-argument-name. Physics variables are often poor code variables
"N806", # non-lowercase-variable-in-function. Physics variables are often poor code variables

# pandas-vet (PD)
"PD",

# flake8-self (SLF)
"SLF001", # private member access

# flake8-todos (TD)
"TD002", # Missing author in TODO

# Ruff-specific rules (RUF)
"RUF005", # unpack-instead-of-concatenating-to-collection-literal -- it's not clearly faster.
]

[tool.ruff.lint.extend-per-file-ignores]
"setup.py" = ["INP001"] # Part of configuration, not a package.
".github/workflows/*.py" = ["INP001"]
"test_*.py" = [
"B018", # UselessExpression
"D", # pydocstyle
"E402", # Module level import not at top of file
"PGH001", # No builtin eval() allowed
"S101", # Use of assert detected
]
".pyinstaller/*.py" = ["INP001"] # Not a package.
"conftest.py" = ["INP001"] # Part of configuration, not a package.
"docs/*.py" = [
"INP001", # implicit-namespace-package. The examples are not a package.
]

[tool.ruff.lint.flake8-annotations]
ignore-fully-untyped = true
mypy-init-return = true

[tool.ruff.lint.isort]
known-first-party = ["astropy", "extension_helpers"]

[tool.ruff.lint.pydocstyle]
convention = "numpy"

0 comments on commit ecd349e

Please sign in to comment.