Skip to content

Commit

Permalink
Enable all ruff rules except those explicitly ignored (#2788)
Browse files Browse the repository at this point in the history
* Enable all ruff rules except those explicitly ignored

* Address all lint errors

* Run mypy against examples
  • Loading branch information
sloria authored Jan 18, 2025
1 parent 56178cf commit 2ec6555
Show file tree
Hide file tree
Showing 25 changed files with 398 additions and 318 deletions.
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ ci:
autoupdate_schedule: monthly
repos:
- repo: /~https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.5
rev: v0.9.2
hooks:
- id: ruff
- id: ruff-format
- repo: /~https://github.com/python-jsonschema/check-jsonschema
rev: 0.30.0
rev: 0.31.0
hooks:
- id: check-github-workflows
- id: check-readthedocs
Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
master_doc = "index"

project = "marshmallow"
copyright = "Steven Loria and contributors"
copyright = "Steven Loria and contributors" # noqa: A001

version = release = importlib.metadata.version("marshmallow")

Expand Down
4 changes: 2 additions & 2 deletions examples/flask_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Quote(db.Model): # type: ignore[name-defined]
id: Mapped[int] = mapped_column(primary_key=True)
content: Mapped[str] = mapped_column(nullable=False)
author_id: Mapped[int] = mapped_column(db.ForeignKey(Author.id))
author: Mapped[Author] = relationship(backref=db.backref("quotes", lazy="dynamic")) # type: ignore[assignment]
author: Mapped[Author] = relationship(backref=db.backref("quotes", lazy="dynamic"))
posted_at: Mapped[datetime.datetime]


Expand Down Expand Up @@ -77,7 +77,7 @@ def process_author(self, data, **kwargs):
author_name = data.get("author")
if author_name:
first, last = author_name.split(" ")
author_dict = dict(first=first, last=last)
author_dict = {"first": first, "last": last}
else:
author_dict = {}
data["author"] = author_dict
Expand Down
32 changes: 15 additions & 17 deletions performance/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Uses the `timeit` module to benchmark serializing an object through marshmallow.
"""

# ruff: noqa: A002, T201
import argparse
import cProfile
import datetime
Expand Down Expand Up @@ -81,7 +82,7 @@ def __init__(
self.col_number = col_number


def run_timeit(quotes, iterations, repeat, profile=False):
def run_timeit(quotes, iterations, repeat, *, profile=False):
quotes_schema = QuoteSchema(many=True)
if profile:
profile = cProfile.Profile()
Expand All @@ -100,8 +101,7 @@ def run_timeit(quotes, iterations, repeat, profile=False):
profile.disable()
profile.dump_stats("marshmallow.pprof")

usec = best * 1e6 / iterations / len(quotes)
return usec
return best * 1e6 / iterations / len(quotes)


def main():
Expand Down Expand Up @@ -129,21 +129,19 @@ def main():
)
args = parser.parse_args()

quotes = []

for i in range(args.object_count):
quotes.append(
Quote(
i,
Author(i, "Foo", "Bar", 42, 66, "123 Fake St"),
"Hello World",
datetime.datetime(2019, 7, 4, tzinfo=datetime.timezone.utc),
"The World",
34,
3,
70,
)
quotes = [
Quote(
i,
Author(i, "Foo", "Bar", 42, 66, "123 Fake St"),
"Hello World",
datetime.datetime(2019, 7, 4, tzinfo=datetime.timezone.utc),
"The World",
34,
3,
70,
)
for i in range(args.object_count)
]

print(
f"Benchmark Result: {run_timeit(quotes, args.iterations, args.repeat, profile=args.profile):.2f} usec/dump"
Expand Down
59 changes: 49 additions & 10 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,60 @@ output-format = "full"
docstring-code-format = true

[tool.ruff.lint]
ignore = ["E203", "E266", "E501", "E731"]
select = [
"B", # flake8-bugbear
"E", # pycodestyle error
"F", # pyflakes
"I", # isort
"UP", # pyupgrade
"W", # pycodestyle warning
# use all checks available in ruff except the ones explicitly ignored below
select = ["ALL"]
ignore = [
"A005", # "module {name} shadows a Python standard-library module"
"ANN", # let mypy handle annotation checks
"ARG", # unused arguments are common w/ interfaces
"COM", # let formatter take care commas
"C901", # don't enforce complexity level
"D", # don't require docstrings
"DTZ007", # ignore false positives due to /~https://github.com/astral-sh/ruff/issues/1306
"E501", # leave line-length enforcement to formatter
"EM", # allow string messages in exceptions
"FIX", # allow "FIX" comments in code
"INP001", # allow Python files outside of packages
"N806", # allow uppercase variable names for variables that are classes
"PERF203", # allow try-except within loops
"PLR0913", # "Too many arguments"
"PLR0912", # "Too many branches"
"PLR2004", # "Magic value used in comparison"
"PTH", # don't require using pathlib instead of os
"RUF012", # allow mutable class variables
"SIM102", # Sometimes nested ifs are more readable than if...and...
"SIM105", # "Use `contextlib.suppress(...)` instead of `try`-`except`-`pass`"
"SIM108", # sometimes if-else is more readable than a ternary
"TD", # allow TODO comments to be whatever we want
"TRY003", # allow long messages passed to exceptions
"TRY004", # allow ValueError for invalid argument types
]

[tool.ruff.lint.per-file-ignores]
"tests/*" = ["E721"]
"tests/*" = [
"ARG", # unused arguments are fine in tests
"C408", # allow dict() instead of dict literal
"DTZ", # allow naive datetimes
"FBT003", # allow boolean positional argument
"N803", # fixture names might be uppercase
"PLR0915", # allow lots of statements
"PT007", # ignore false positives due to /~https://github.com/astral-sh/ruff/issues/14743
"PT011", # don't require match when using pytest.raises
"S", # allow asserts
"SIM117", # allow nested with statements because it's more readable sometimes
"SLF001", # allow private attribute access
]
"examples/*" = [
"S", # allow asserts
"T", # allow prints
]
"src/marshmallow/orderedset.py" = [
"FBT002", # allow boolean positional argument
"T", # allow prints
]

[tool.mypy]
files = ["src", "tests"]
files = ["src", "tests", "examples"]
ignore_missing_imports = true
warn_unreachable = true
warn_unused_ignores = true
Expand Down
12 changes: 6 additions & 6 deletions src/marshmallow/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,14 @@ def __getattr__(name: str) -> typing.Any:
"RAISE",
"Schema",
"SchemaOpts",
"ValidationError",
"fields",
"validates",
"validates_schema",
"pre_dump",
"missing",
"post_dump",
"pre_load",
"post_load",
"pprint",
"ValidationError",
"missing",
"pre_dump",
"pre_load",
"validates",
"validates_schema",
]
15 changes: 8 additions & 7 deletions src/marshmallow/class_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
This module is treated as private API.
Users should not need to use this module directly.
"""
# ruff: noqa: ERA001

from __future__ import annotations

Expand Down Expand Up @@ -49,7 +50,7 @@ class MyClass:
module = cls.__module__
# Full module path to the class
# e.g. user.schemas.UserSchema
fullpath = ".".join([module, classname])
fullpath = f"{module}.{classname}"
# If the class is already registered; need to check if the entries are
# in the same module as cls to avoid having multiple instances of the same
# class in the registry
Expand All @@ -66,18 +67,19 @@ class MyClass:
else:
# If fullpath does exist, replace existing entry
_registry[fullpath] = [cls]
return None


@typing.overload
def get_class(classname: str, all: typing.Literal[False] = ...) -> SchemaType: ...
def get_class(classname: str, *, all: typing.Literal[False] = ...) -> SchemaType: ...


@typing.overload
def get_class(classname: str, all: typing.Literal[True] = ...) -> list[SchemaType]: ...
def get_class(
classname: str, *, all: typing.Literal[True] = ...
) -> list[SchemaType]: ...


def get_class(classname: str, all: bool = False) -> list[SchemaType] | SchemaType:
def get_class(classname: str, *, all: bool = False) -> list[SchemaType] | SchemaType: # noqa: A002
"""Retrieve a class from the registry.
:raises: `marshmallow.exceptions.RegistryError` if the class cannot be found
Expand All @@ -98,5 +100,4 @@ def get_class(classname: str, all: bool = False) -> list[SchemaType] | SchemaTyp
"were found. Please use the full, "
"module-qualified path."
)
else:
return _registry[classname][0]
return _registry[classname][0]
25 changes: 15 additions & 10 deletions src/marshmallow/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ def validates(field_name: str) -> Callable[..., Any]:

def validates_schema(
fn: Callable[..., Any] | None = None,
pass_many: bool = False,
pass_original: bool = False,
skip_on_field_errors: bool = True,
pass_many: bool = False, # noqa: FBT001, FBT002
pass_original: bool = False, # noqa: FBT001, FBT002
skip_on_field_errors: bool = True, # noqa: FBT001, FBT002
) -> Callable[..., Any]:
"""Register a schema-level validator.
Expand Down Expand Up @@ -126,7 +126,8 @@ def validates_schema(


def pre_dump(
fn: Callable[..., Any] | None = None, pass_many: bool = False
fn: Callable[..., Any] | None = None,
pass_many: bool = False, # noqa: FBT001, FBT002
) -> Callable[..., Any]:
"""Register a method to invoke before serializing an object. The method
receives the object to be serialized and returns the processed object.
Expand All @@ -143,8 +144,8 @@ def pre_dump(

def post_dump(
fn: Callable[..., Any] | None = None,
pass_many: bool = False,
pass_original: bool = False,
pass_many: bool = False, # noqa: FBT001, FBT002
pass_original: bool = False, # noqa: FBT001, FBT002
) -> Callable[..., Any]:
"""Register a method to invoke after serializing an object. The method
receives the serialized object and returns the processed object.
Expand All @@ -163,7 +164,8 @@ def post_dump(


def pre_load(
fn: Callable[..., Any] | None = None, pass_many: bool = False
fn: Callable[..., Any] | None = None,
pass_many: bool = False, # noqa: FBT001, FBT002
) -> Callable[..., Any]:
"""Register a method to invoke before deserializing an object. The method
receives the data to be deserialized and returns the processed data.
Expand All @@ -181,8 +183,8 @@ def pre_load(

def post_load(
fn: Callable[..., Any] | None = None,
pass_many: bool = False,
pass_original: bool = False,
pass_many: bool = False, # noqa: FBT001, FBT002
pass_original: bool = False, # noqa: FBT001, FBT002
) -> Callable[..., Any]:
"""Register a method to invoke after deserializing an object. The method
receives the deserialized data and returns the processed data.
Expand All @@ -202,7 +204,10 @@ def post_load(


def set_hook(
fn: Callable[..., Any] | None, tag: str, many: bool = False, **kwargs: Any
fn: Callable[..., Any] | None,
tag: str,
many: bool = False, # noqa: FBT001, FBT002
**kwargs: Any,
) -> Callable[..., Any]:
"""Mark decorated function as a hook to be picked up later.
You should not need to use this method directly.
Expand Down
6 changes: 3 additions & 3 deletions src/marshmallow/error_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def store_error(self, messages, field_name=SCHEMA, index=None):
self.errors = merge_errors(self.errors, messages)


def merge_errors(errors1, errors2):
def merge_errors(errors1, errors2): # noqa: PLR0911
"""Deeply merge two error messages.
The format of ``errors1`` and ``errors2`` matches the ``message``
Expand All @@ -40,7 +40,7 @@ def merge_errors(errors1, errors2):
return errors1 + errors2
if isinstance(errors2, dict):
return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
return errors1 + [errors2]
return [*errors1, errors2]
if isinstance(errors1, dict):
if isinstance(errors2, list):
return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
Expand All @@ -54,7 +54,7 @@ def merge_errors(errors1, errors2):
return errors
return dict(errors1, **{SCHEMA: merge_errors(errors1.get(SCHEMA), errors2)})
if isinstance(errors2, list):
return [errors1] + errors2
return [errors1, *errors2]
if isinstance(errors2, dict):
return dict(errors2, **{SCHEMA: merge_errors(errors1, errors2.get(SCHEMA))})
return [errors1, errors2]
Loading

0 comments on commit 2ec6555

Please sign in to comment.