Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ruff rules and docs #2478

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these global ignores?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, these ignores should apply globally.

# 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 = ["tardis", "carsus", "stardis"]

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