From 57bae7a2fee61b30caed9f3c0ab5b3ba11527e1c Mon Sep 17 00:00:00 2001 From: CoprDistGit Date: Mon, 10 Apr 2023 21:29:29 +0000 Subject: automatic import of python-flake8-pie --- .gitignore | 1 + python-flake8-pie.spec | 1801 ++++++++++++++++++++++++++++++++++++++++++++++++ sources | 1 + 3 files changed, 1803 insertions(+) create mode 100644 python-flake8-pie.spec create mode 100644 sources diff --git a/.gitignore b/.gitignore index e69de29..7c9cf36 100644 --- a/.gitignore +++ b/.gitignore @@ -0,0 +1 @@ +/flake8-pie-0.16.0.tar.gz diff --git a/python-flake8-pie.spec b/python-flake8-pie.spec new file mode 100644 index 0000000..98d2abc --- /dev/null +++ b/python-flake8-pie.spec @@ -0,0 +1,1801 @@ +%global _empty_manifest_terminate_build 0 +Name: python-flake8-pie +Version: 0.16.0 +Release: 1 +Summary: A flake8 extension that implements misc. lints +License: BSD-2-Clause +URL: https://github.com/sbdchd/flake8-pie +Source0: https://mirrors.nju.edu.cn/pypi/web/packages/ae/11/cebfe54fda9897188ca40d1cd755d1be13908ceedfea9e500fae162f06a9/flake8-pie-0.16.0.tar.gz +BuildArch: noarch + +Requires: python3-typing_extensions + +%description +# flake8-pie [![CircleCI](https://circleci.com/gh/sbdchd/flake8-pie.svg?style=svg)](https://circleci.com/gh/sbdchd/flake8-pie) [![pypi](https://img.shields.io/pypi/v/flake8-pie.svg)](https://pypi.org/project/flake8-pie/) + +> A flake8 extension that implements misc. lints + +## lints + +### PIE781: assign-and-return + +Based on Clippy's +[`let_and_return`](https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return) +and Microsoft's TSLint rule +[`no-unnecessary-local-variable`](https://github.com/Microsoft/tslint-microsoft-contrib). + +For more info on the structure of this lint, see the [accompanying blog +post](https://steve.dignam.xyz/2018/12/16/creating-a-flake8-lint/). + +#### examples + +```python +# error +def foo(): + x = bar() + return x + +# allowed +def foo(): + x, _ = bar() + return x +``` + +### PIE783: celery-explicit-names + +Warn about [Celery](https://pypi.org/project/celery/) task definitions that don't have explicit names. + +Note: this lint is kind of naive considering any decorator with a `.task()` +method or any decorator called `shared_task()` a Celery decorator. + +#### examples + +```python +# error +@app.task() +def foo(): + pass + +# ok +@app.task(name="app_name.tasks.foo") +def foo(): + pass +``` + +### PIE784: celery-explicit-crontab-args + +The `crontab` class provided by Celery has some default args that are +suprising to new users. Specifically, `crontab(hour="0,12")` won't run a task +at midnight and noon, it will run the task at every minute during those two +hours. This lint makes that call an error, forcing you to write +`crontab(hour="0, 12", minute="*")`. + +Additionally, the lint is a bit more complex in that it requires you specify +every smaller increment than the largest time increment you provide. So if you +provide `days_of_week`, then you need to provide `hour`s and `minute`s +explicitly. + +Note: if you like the default behavior of `crontab()` then you can either +disable this lint or pass `"*"` for the `kwarg` value, e.g., `minutes="*"`. + +Also, since this lint is essentially a naive search for calls to a +`crontab()` function, if you have a function named the same then this will +cause false positives. + +### PIE785: celery-require-tasks-expire + +Celery tasks can bunch up if they don't have expirations. + +This enforces specifying expirations in both the celery beat config dict and +in `.apply_async()` calls. + +The same caveat applies about how this lint is naive. + +### PIE786: precise-exception-handlers + +Be precise in what exceptions you catch. Bare `except:` handlers, catching `BaseException`, or catching `Exception` can lead to unexpected bugs. + +#### examples + +```python +# error +try: + save_file(name="export.csv") +except: + pass + +# error +try: + save_file(name="export.csv") +except BaseException: + pass + +# error +try: + save_file(name="export.csv") +except Exception: + pass + +# error +try: + save_file(name="export.csv") +except (ValueError, Exception): + pass + + +# ok +try: + save_file(name="export.csv") +except OSError: + pass +``` + +### PIE787: no-len-condition + +Empty collections are fasley in Python so calling `len()` is unnecessary when +checking for emptiness in an if statement/expression. + +Comparing to explicit scalars is allowed. + +```python +# error +if len(foo): ... +if not len(foo): ... + +# ok +if foo: ... +if not foo: ... +if len(foo) > 0: ... +if len(foo) == 0: ... +``` + +### PIE788: no-bool-condition + +If statements/expressions evalute the truthiness of the their test argument, +so calling `bool()` is unnecessary. + +Comparing to `True`/`False` is allowed. + +```python +# error +if bool(foo): ... +if not bool(foo): ... + +# ok +if foo: ... +if not foo: ... +if bool(foo) is True: ... +if bool(foo) is False: ... +``` + +### PIE789: prefer-isinstance-type-compare + +Using `type()` doesn't take into account subclassess and type checkers won't +refine the type, use `isinstance` instead. + +```python +# error +if type(foo) == str: ... +if type(foo) is str: ... +if type(foo) in [int, str]: ... + +# ok +if isinstance(foo, str): ... +if isinstance(foo, (int, str)): ... +``` + +### PIE790: no-unnecessary-pass + +`pass` is unnecessary when definining a `class` or function with an empty +body. + +```python +# error +class BadError(Exception): + """ + some doc comment + """ + pass + +def foo() -> None: + """ + some function + """ + pass + +# ok +class BadError(Exception): + """ + some doc comment + """ + +def foo() -> None: + """ + some function + """ +``` + +### PIE791: no-pointless-statements + +Comparisions without an assignment or assertion are probably a typo. + +```python +# error +"foobar" in data +res.json() == [] +user.is_authenticated() is True + +# ok +assert "foobar" in data +foo = res.json() == [] +use.is_authenticated() +``` + +### PIE792: no-inherit-object + +Inheriting from `object` isn't necessary in Python 3. + +```python +# error +class Foo(object): + ... + +# ok +class Foo: + ... +``` + +### PIE793: prefer-dataclass + +Attempts to find cases where the `@dataclass` decorator is unintentionally +missing. + +```python +# error +class Foo: + z: dict[int, int] + def __init__(self) -> None: ... + +class Bar: + x: list[str] + +# ok +class Bar(Foo): + z: dict[int, int] + +@dataclass +class Bar: + x: list[str] +``` + +### PIE794: dupe-class-field-definitions + +Finds duplicate definitions for the same field, which can occur in large ORM +model definitions. + +```python +# error +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... + email = fields.EmailField() + +# ok +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... +``` + +### PIE795: prefer-stdlib-enums + +Instead of defining various constant properties on a class, use the stdlib +enum which typecheckers support for type refinement. + +```python +# error +class Foo: + A = "A" + B = "B" + C = "C" + +# ok +import enum +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" +``` + +### PIE796: prefer-unique-enums + +By default the stdlib enum allows multiple field names to map to the same +value, this lint requires each enum value be unique. + +```python +# error +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "C" + +# ok +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "D" +``` + +### PIE797: no-unnecessary-if-expr + +Call `bool()` directly rather than reimplementing its functionality. + +```python +# error +foo(is_valid=True if buzz() else False) + +# ok +foo(is_valid=bool(buzz())) +``` + +### PIE798: no-unnecessary-class + +Instead of using class to namespace functions, use a module. + +```python +# error +class UserManager: + class User(NamedTuple): + name: str + + @classmethod + def update_user(cls, user: User) -> None: + ... + + @staticmethod + def sync_users() -> None: + ... + +# ok +class User(NamedTuple): + name: str + +def update_user(user: User) -> None: + ... + +def sync_users() -> None: + ... +``` + +### PIE799: prefer-col-init + +Check that values are passed in when collections are created rather than +creating an empty collection and then inserting. + +```python +# error +bars = [] +bar = bar() +bars.append(bar) + +# ok +bar = bar() +bars = [bar] + +# error +s = deque() +s.append(foo) + +# ok +s = deque([foo]) +``` + +### PIE800: no-unnecessary-spread + +Check for unnecessary dict unpacking. + +```python +# error +{**foo, **{"bar": 10}} + +# ok +{**foo, "bar": 10} +``` + +### PIE801: prefer-simple-return + +Return boolean expressions directly instead of returning `True` and `False`. + +```python +# error +def main(): + if foo > 5: + return True + return False + +# error +def main(): + if foo > 5: + return True + else: + return False + +# ok +def main(): + return foo > 5 +``` + +### PIE802: prefer-simple-any-all + +Remove unnecessary comprehensions for `any` and `all` + +```python +# error +any([x.id for x in bar]) +all([x.id for x in bar]) + +# ok +all(x.id for x in bar) +any(x.id for x in bar) +any({x.id for x in bar}) +``` + +### PIE803: prefer-logging-interpolation + +Don't format strings before logging. Let `logging` interpolate arguments. + +This allows Sentry to aggregate logs, prevents raising exceptions if interpolation fails, and improves performance if the log level is disabled. See ["PyCQA/pylint#1788"](https://github.com/PyCQA/pylint/issues/1788#issuecomment-461279687). + +```python +# error +logger.info("Login error for %s" % user) +logger.info("Login error for %s, %s" % (user_id, name)) + +# error +logger.info("Login error for {}".format(user)) +logger.info("Login error for {}, {}".format(user_id, name)) + +# error +logger.info(f"Login error for {user}") +logger.info(f"Login error for {user_id}, {name}") + +# ok +logger.info("Login error for %s", user) +logger.info("Login error for %s, %s", user_id, name) +``` + +### PIE804: no-unnecessary-dict-kwargs + +As long as the keys of the dict are valid Python identifier names, we can safely +remove the surrounding dict. + +```python +# error +foo(**{"bar": True}) + +# ok +foo(bar=True) +foo(**buzz) +foo(**{"bar foo": True}) +``` + +## dev + +```shell +# install dependencies +poetry install + +s/lint +s/test +``` + +### PIE805: prefer-literal + +Currently only checks for byte string literals. + +```python +# error +"foo".encode() + +# ok +b"foo" +"😀".encode() +``` + +### PIE806: no-assert-except + +Instead of `assert`ing and catching the exception, use an if statement. + +```python +# error +try: + assert "@" in bar +except AssertionError: + ... + +# ok +if "@" in bar: + ... +``` + +### PIE807: prefer-list-builtin + +`lambda: []` is equivalent to the builtin `list` + +```python +# error +@dataclass +class Foo: + foo: List[str] = field(default_factory=lambda: []) + +# ok +@dataclass +class Foo: + foo: List[str] = field(default_factory=list) +``` + +### PIE808: prefer-simple-range + +We can leave out the first argument to `range` in some cases since the default +start position is 0. + +```python +# err +range(0, 10) + +# ok +range(10) +range(x, 10) +range(0, 10, x) +``` + +### PIE809: django-prefer-bulk + +Bulk create multiple objects instead of executing O(N) queries. + +```python +# error +[Item.objects.create(item) for item in items] + +# error +[Item.objects.create(item) for item in [bar for bar in buzz]] + +# error +(Item.objects.create(item) for item in items) + +# ok +Item.objects.insert(items) +Item.objects.create(item) +``` + +## development + +### examining the AST +You can use `astpretty` to dump the AST of a piece of code. + +```shell +./.venv/bin/astpretty <(pbpaste) +``` + +### uploading a new version to [PyPi](https://pypi.org) + +```shell +# increment `Flake8PieCheck.version` and pyproject.toml `version` + +# build new distribution files and upload to pypi +# Note: this will ask for login credentials +rm -rf dist && poetry publish --build +``` + + +%package -n python3-flake8-pie +Summary: A flake8 extension that implements misc. lints +Provides: python-flake8-pie +BuildRequires: python3-devel +BuildRequires: python3-setuptools +BuildRequires: python3-pip +%description -n python3-flake8-pie +# flake8-pie [![CircleCI](https://circleci.com/gh/sbdchd/flake8-pie.svg?style=svg)](https://circleci.com/gh/sbdchd/flake8-pie) [![pypi](https://img.shields.io/pypi/v/flake8-pie.svg)](https://pypi.org/project/flake8-pie/) + +> A flake8 extension that implements misc. lints + +## lints + +### PIE781: assign-and-return + +Based on Clippy's +[`let_and_return`](https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return) +and Microsoft's TSLint rule +[`no-unnecessary-local-variable`](https://github.com/Microsoft/tslint-microsoft-contrib). + +For more info on the structure of this lint, see the [accompanying blog +post](https://steve.dignam.xyz/2018/12/16/creating-a-flake8-lint/). + +#### examples + +```python +# error +def foo(): + x = bar() + return x + +# allowed +def foo(): + x, _ = bar() + return x +``` + +### PIE783: celery-explicit-names + +Warn about [Celery](https://pypi.org/project/celery/) task definitions that don't have explicit names. + +Note: this lint is kind of naive considering any decorator with a `.task()` +method or any decorator called `shared_task()` a Celery decorator. + +#### examples + +```python +# error +@app.task() +def foo(): + pass + +# ok +@app.task(name="app_name.tasks.foo") +def foo(): + pass +``` + +### PIE784: celery-explicit-crontab-args + +The `crontab` class provided by Celery has some default args that are +suprising to new users. Specifically, `crontab(hour="0,12")` won't run a task +at midnight and noon, it will run the task at every minute during those two +hours. This lint makes that call an error, forcing you to write +`crontab(hour="0, 12", minute="*")`. + +Additionally, the lint is a bit more complex in that it requires you specify +every smaller increment than the largest time increment you provide. So if you +provide `days_of_week`, then you need to provide `hour`s and `minute`s +explicitly. + +Note: if you like the default behavior of `crontab()` then you can either +disable this lint or pass `"*"` for the `kwarg` value, e.g., `minutes="*"`. + +Also, since this lint is essentially a naive search for calls to a +`crontab()` function, if you have a function named the same then this will +cause false positives. + +### PIE785: celery-require-tasks-expire + +Celery tasks can bunch up if they don't have expirations. + +This enforces specifying expirations in both the celery beat config dict and +in `.apply_async()` calls. + +The same caveat applies about how this lint is naive. + +### PIE786: precise-exception-handlers + +Be precise in what exceptions you catch. Bare `except:` handlers, catching `BaseException`, or catching `Exception` can lead to unexpected bugs. + +#### examples + +```python +# error +try: + save_file(name="export.csv") +except: + pass + +# error +try: + save_file(name="export.csv") +except BaseException: + pass + +# error +try: + save_file(name="export.csv") +except Exception: + pass + +# error +try: + save_file(name="export.csv") +except (ValueError, Exception): + pass + + +# ok +try: + save_file(name="export.csv") +except OSError: + pass +``` + +### PIE787: no-len-condition + +Empty collections are fasley in Python so calling `len()` is unnecessary when +checking for emptiness in an if statement/expression. + +Comparing to explicit scalars is allowed. + +```python +# error +if len(foo): ... +if not len(foo): ... + +# ok +if foo: ... +if not foo: ... +if len(foo) > 0: ... +if len(foo) == 0: ... +``` + +### PIE788: no-bool-condition + +If statements/expressions evalute the truthiness of the their test argument, +so calling `bool()` is unnecessary. + +Comparing to `True`/`False` is allowed. + +```python +# error +if bool(foo): ... +if not bool(foo): ... + +# ok +if foo: ... +if not foo: ... +if bool(foo) is True: ... +if bool(foo) is False: ... +``` + +### PIE789: prefer-isinstance-type-compare + +Using `type()` doesn't take into account subclassess and type checkers won't +refine the type, use `isinstance` instead. + +```python +# error +if type(foo) == str: ... +if type(foo) is str: ... +if type(foo) in [int, str]: ... + +# ok +if isinstance(foo, str): ... +if isinstance(foo, (int, str)): ... +``` + +### PIE790: no-unnecessary-pass + +`pass` is unnecessary when definining a `class` or function with an empty +body. + +```python +# error +class BadError(Exception): + """ + some doc comment + """ + pass + +def foo() -> None: + """ + some function + """ + pass + +# ok +class BadError(Exception): + """ + some doc comment + """ + +def foo() -> None: + """ + some function + """ +``` + +### PIE791: no-pointless-statements + +Comparisions without an assignment or assertion are probably a typo. + +```python +# error +"foobar" in data +res.json() == [] +user.is_authenticated() is True + +# ok +assert "foobar" in data +foo = res.json() == [] +use.is_authenticated() +``` + +### PIE792: no-inherit-object + +Inheriting from `object` isn't necessary in Python 3. + +```python +# error +class Foo(object): + ... + +# ok +class Foo: + ... +``` + +### PIE793: prefer-dataclass + +Attempts to find cases where the `@dataclass` decorator is unintentionally +missing. + +```python +# error +class Foo: + z: dict[int, int] + def __init__(self) -> None: ... + +class Bar: + x: list[str] + +# ok +class Bar(Foo): + z: dict[int, int] + +@dataclass +class Bar: + x: list[str] +``` + +### PIE794: dupe-class-field-definitions + +Finds duplicate definitions for the same field, which can occur in large ORM +model definitions. + +```python +# error +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... + email = fields.EmailField() + +# ok +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... +``` + +### PIE795: prefer-stdlib-enums + +Instead of defining various constant properties on a class, use the stdlib +enum which typecheckers support for type refinement. + +```python +# error +class Foo: + A = "A" + B = "B" + C = "C" + +# ok +import enum +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" +``` + +### PIE796: prefer-unique-enums + +By default the stdlib enum allows multiple field names to map to the same +value, this lint requires each enum value be unique. + +```python +# error +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "C" + +# ok +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "D" +``` + +### PIE797: no-unnecessary-if-expr + +Call `bool()` directly rather than reimplementing its functionality. + +```python +# error +foo(is_valid=True if buzz() else False) + +# ok +foo(is_valid=bool(buzz())) +``` + +### PIE798: no-unnecessary-class + +Instead of using class to namespace functions, use a module. + +```python +# error +class UserManager: + class User(NamedTuple): + name: str + + @classmethod + def update_user(cls, user: User) -> None: + ... + + @staticmethod + def sync_users() -> None: + ... + +# ok +class User(NamedTuple): + name: str + +def update_user(user: User) -> None: + ... + +def sync_users() -> None: + ... +``` + +### PIE799: prefer-col-init + +Check that values are passed in when collections are created rather than +creating an empty collection and then inserting. + +```python +# error +bars = [] +bar = bar() +bars.append(bar) + +# ok +bar = bar() +bars = [bar] + +# error +s = deque() +s.append(foo) + +# ok +s = deque([foo]) +``` + +### PIE800: no-unnecessary-spread + +Check for unnecessary dict unpacking. + +```python +# error +{**foo, **{"bar": 10}} + +# ok +{**foo, "bar": 10} +``` + +### PIE801: prefer-simple-return + +Return boolean expressions directly instead of returning `True` and `False`. + +```python +# error +def main(): + if foo > 5: + return True + return False + +# error +def main(): + if foo > 5: + return True + else: + return False + +# ok +def main(): + return foo > 5 +``` + +### PIE802: prefer-simple-any-all + +Remove unnecessary comprehensions for `any` and `all` + +```python +# error +any([x.id for x in bar]) +all([x.id for x in bar]) + +# ok +all(x.id for x in bar) +any(x.id for x in bar) +any({x.id for x in bar}) +``` + +### PIE803: prefer-logging-interpolation + +Don't format strings before logging. Let `logging` interpolate arguments. + +This allows Sentry to aggregate logs, prevents raising exceptions if interpolation fails, and improves performance if the log level is disabled. See ["PyCQA/pylint#1788"](https://github.com/PyCQA/pylint/issues/1788#issuecomment-461279687). + +```python +# error +logger.info("Login error for %s" % user) +logger.info("Login error for %s, %s" % (user_id, name)) + +# error +logger.info("Login error for {}".format(user)) +logger.info("Login error for {}, {}".format(user_id, name)) + +# error +logger.info(f"Login error for {user}") +logger.info(f"Login error for {user_id}, {name}") + +# ok +logger.info("Login error for %s", user) +logger.info("Login error for %s, %s", user_id, name) +``` + +### PIE804: no-unnecessary-dict-kwargs + +As long as the keys of the dict are valid Python identifier names, we can safely +remove the surrounding dict. + +```python +# error +foo(**{"bar": True}) + +# ok +foo(bar=True) +foo(**buzz) +foo(**{"bar foo": True}) +``` + +## dev + +```shell +# install dependencies +poetry install + +s/lint +s/test +``` + +### PIE805: prefer-literal + +Currently only checks for byte string literals. + +```python +# error +"foo".encode() + +# ok +b"foo" +"😀".encode() +``` + +### PIE806: no-assert-except + +Instead of `assert`ing and catching the exception, use an if statement. + +```python +# error +try: + assert "@" in bar +except AssertionError: + ... + +# ok +if "@" in bar: + ... +``` + +### PIE807: prefer-list-builtin + +`lambda: []` is equivalent to the builtin `list` + +```python +# error +@dataclass +class Foo: + foo: List[str] = field(default_factory=lambda: []) + +# ok +@dataclass +class Foo: + foo: List[str] = field(default_factory=list) +``` + +### PIE808: prefer-simple-range + +We can leave out the first argument to `range` in some cases since the default +start position is 0. + +```python +# err +range(0, 10) + +# ok +range(10) +range(x, 10) +range(0, 10, x) +``` + +### PIE809: django-prefer-bulk + +Bulk create multiple objects instead of executing O(N) queries. + +```python +# error +[Item.objects.create(item) for item in items] + +# error +[Item.objects.create(item) for item in [bar for bar in buzz]] + +# error +(Item.objects.create(item) for item in items) + +# ok +Item.objects.insert(items) +Item.objects.create(item) +``` + +## development + +### examining the AST +You can use `astpretty` to dump the AST of a piece of code. + +```shell +./.venv/bin/astpretty <(pbpaste) +``` + +### uploading a new version to [PyPi](https://pypi.org) + +```shell +# increment `Flake8PieCheck.version` and pyproject.toml `version` + +# build new distribution files and upload to pypi +# Note: this will ask for login credentials +rm -rf dist && poetry publish --build +``` + + +%package help +Summary: Development documents and examples for flake8-pie +Provides: python3-flake8-pie-doc +%description help +# flake8-pie [![CircleCI](https://circleci.com/gh/sbdchd/flake8-pie.svg?style=svg)](https://circleci.com/gh/sbdchd/flake8-pie) [![pypi](https://img.shields.io/pypi/v/flake8-pie.svg)](https://pypi.org/project/flake8-pie/) + +> A flake8 extension that implements misc. lints + +## lints + +### PIE781: assign-and-return + +Based on Clippy's +[`let_and_return`](https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return) +and Microsoft's TSLint rule +[`no-unnecessary-local-variable`](https://github.com/Microsoft/tslint-microsoft-contrib). + +For more info on the structure of this lint, see the [accompanying blog +post](https://steve.dignam.xyz/2018/12/16/creating-a-flake8-lint/). + +#### examples + +```python +# error +def foo(): + x = bar() + return x + +# allowed +def foo(): + x, _ = bar() + return x +``` + +### PIE783: celery-explicit-names + +Warn about [Celery](https://pypi.org/project/celery/) task definitions that don't have explicit names. + +Note: this lint is kind of naive considering any decorator with a `.task()` +method or any decorator called `shared_task()` a Celery decorator. + +#### examples + +```python +# error +@app.task() +def foo(): + pass + +# ok +@app.task(name="app_name.tasks.foo") +def foo(): + pass +``` + +### PIE784: celery-explicit-crontab-args + +The `crontab` class provided by Celery has some default args that are +suprising to new users. Specifically, `crontab(hour="0,12")` won't run a task +at midnight and noon, it will run the task at every minute during those two +hours. This lint makes that call an error, forcing you to write +`crontab(hour="0, 12", minute="*")`. + +Additionally, the lint is a bit more complex in that it requires you specify +every smaller increment than the largest time increment you provide. So if you +provide `days_of_week`, then you need to provide `hour`s and `minute`s +explicitly. + +Note: if you like the default behavior of `crontab()` then you can either +disable this lint or pass `"*"` for the `kwarg` value, e.g., `minutes="*"`. + +Also, since this lint is essentially a naive search for calls to a +`crontab()` function, if you have a function named the same then this will +cause false positives. + +### PIE785: celery-require-tasks-expire + +Celery tasks can bunch up if they don't have expirations. + +This enforces specifying expirations in both the celery beat config dict and +in `.apply_async()` calls. + +The same caveat applies about how this lint is naive. + +### PIE786: precise-exception-handlers + +Be precise in what exceptions you catch. Bare `except:` handlers, catching `BaseException`, or catching `Exception` can lead to unexpected bugs. + +#### examples + +```python +# error +try: + save_file(name="export.csv") +except: + pass + +# error +try: + save_file(name="export.csv") +except BaseException: + pass + +# error +try: + save_file(name="export.csv") +except Exception: + pass + +# error +try: + save_file(name="export.csv") +except (ValueError, Exception): + pass + + +# ok +try: + save_file(name="export.csv") +except OSError: + pass +``` + +### PIE787: no-len-condition + +Empty collections are fasley in Python so calling `len()` is unnecessary when +checking for emptiness in an if statement/expression. + +Comparing to explicit scalars is allowed. + +```python +# error +if len(foo): ... +if not len(foo): ... + +# ok +if foo: ... +if not foo: ... +if len(foo) > 0: ... +if len(foo) == 0: ... +``` + +### PIE788: no-bool-condition + +If statements/expressions evalute the truthiness of the their test argument, +so calling `bool()` is unnecessary. + +Comparing to `True`/`False` is allowed. + +```python +# error +if bool(foo): ... +if not bool(foo): ... + +# ok +if foo: ... +if not foo: ... +if bool(foo) is True: ... +if bool(foo) is False: ... +``` + +### PIE789: prefer-isinstance-type-compare + +Using `type()` doesn't take into account subclassess and type checkers won't +refine the type, use `isinstance` instead. + +```python +# error +if type(foo) == str: ... +if type(foo) is str: ... +if type(foo) in [int, str]: ... + +# ok +if isinstance(foo, str): ... +if isinstance(foo, (int, str)): ... +``` + +### PIE790: no-unnecessary-pass + +`pass` is unnecessary when definining a `class` or function with an empty +body. + +```python +# error +class BadError(Exception): + """ + some doc comment + """ + pass + +def foo() -> None: + """ + some function + """ + pass + +# ok +class BadError(Exception): + """ + some doc comment + """ + +def foo() -> None: + """ + some function + """ +``` + +### PIE791: no-pointless-statements + +Comparisions without an assignment or assertion are probably a typo. + +```python +# error +"foobar" in data +res.json() == [] +user.is_authenticated() is True + +# ok +assert "foobar" in data +foo = res.json() == [] +use.is_authenticated() +``` + +### PIE792: no-inherit-object + +Inheriting from `object` isn't necessary in Python 3. + +```python +# error +class Foo(object): + ... + +# ok +class Foo: + ... +``` + +### PIE793: prefer-dataclass + +Attempts to find cases where the `@dataclass` decorator is unintentionally +missing. + +```python +# error +class Foo: + z: dict[int, int] + def __init__(self) -> None: ... + +class Bar: + x: list[str] + +# ok +class Bar(Foo): + z: dict[int, int] + +@dataclass +class Bar: + x: list[str] +``` + +### PIE794: dupe-class-field-definitions + +Finds duplicate definitions for the same field, which can occur in large ORM +model definitions. + +```python +# error +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... + email = fields.EmailField() + +# ok +class User(BaseModel): + email = fields.EmailField() + # ...80 more properties... +``` + +### PIE795: prefer-stdlib-enums + +Instead of defining various constant properties on a class, use the stdlib +enum which typecheckers support for type refinement. + +```python +# error +class Foo: + A = "A" + B = "B" + C = "C" + +# ok +import enum +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" +``` + +### PIE796: prefer-unique-enums + +By default the stdlib enum allows multiple field names to map to the same +value, this lint requires each enum value be unique. + +```python +# error +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "C" + +# ok +class Foo(enum.Enum): + A = "A" + B = "B" + C = "C" + D = "D" +``` + +### PIE797: no-unnecessary-if-expr + +Call `bool()` directly rather than reimplementing its functionality. + +```python +# error +foo(is_valid=True if buzz() else False) + +# ok +foo(is_valid=bool(buzz())) +``` + +### PIE798: no-unnecessary-class + +Instead of using class to namespace functions, use a module. + +```python +# error +class UserManager: + class User(NamedTuple): + name: str + + @classmethod + def update_user(cls, user: User) -> None: + ... + + @staticmethod + def sync_users() -> None: + ... + +# ok +class User(NamedTuple): + name: str + +def update_user(user: User) -> None: + ... + +def sync_users() -> None: + ... +``` + +### PIE799: prefer-col-init + +Check that values are passed in when collections are created rather than +creating an empty collection and then inserting. + +```python +# error +bars = [] +bar = bar() +bars.append(bar) + +# ok +bar = bar() +bars = [bar] + +# error +s = deque() +s.append(foo) + +# ok +s = deque([foo]) +``` + +### PIE800: no-unnecessary-spread + +Check for unnecessary dict unpacking. + +```python +# error +{**foo, **{"bar": 10}} + +# ok +{**foo, "bar": 10} +``` + +### PIE801: prefer-simple-return + +Return boolean expressions directly instead of returning `True` and `False`. + +```python +# error +def main(): + if foo > 5: + return True + return False + +# error +def main(): + if foo > 5: + return True + else: + return False + +# ok +def main(): + return foo > 5 +``` + +### PIE802: prefer-simple-any-all + +Remove unnecessary comprehensions for `any` and `all` + +```python +# error +any([x.id for x in bar]) +all([x.id for x in bar]) + +# ok +all(x.id for x in bar) +any(x.id for x in bar) +any({x.id for x in bar}) +``` + +### PIE803: prefer-logging-interpolation + +Don't format strings before logging. Let `logging` interpolate arguments. + +This allows Sentry to aggregate logs, prevents raising exceptions if interpolation fails, and improves performance if the log level is disabled. See ["PyCQA/pylint#1788"](https://github.com/PyCQA/pylint/issues/1788#issuecomment-461279687). + +```python +# error +logger.info("Login error for %s" % user) +logger.info("Login error for %s, %s" % (user_id, name)) + +# error +logger.info("Login error for {}".format(user)) +logger.info("Login error for {}, {}".format(user_id, name)) + +# error +logger.info(f"Login error for {user}") +logger.info(f"Login error for {user_id}, {name}") + +# ok +logger.info("Login error for %s", user) +logger.info("Login error for %s, %s", user_id, name) +``` + +### PIE804: no-unnecessary-dict-kwargs + +As long as the keys of the dict are valid Python identifier names, we can safely +remove the surrounding dict. + +```python +# error +foo(**{"bar": True}) + +# ok +foo(bar=True) +foo(**buzz) +foo(**{"bar foo": True}) +``` + +## dev + +```shell +# install dependencies +poetry install + +s/lint +s/test +``` + +### PIE805: prefer-literal + +Currently only checks for byte string literals. + +```python +# error +"foo".encode() + +# ok +b"foo" +"😀".encode() +``` + +### PIE806: no-assert-except + +Instead of `assert`ing and catching the exception, use an if statement. + +```python +# error +try: + assert "@" in bar +except AssertionError: + ... + +# ok +if "@" in bar: + ... +``` + +### PIE807: prefer-list-builtin + +`lambda: []` is equivalent to the builtin `list` + +```python +# error +@dataclass +class Foo: + foo: List[str] = field(default_factory=lambda: []) + +# ok +@dataclass +class Foo: + foo: List[str] = field(default_factory=list) +``` + +### PIE808: prefer-simple-range + +We can leave out the first argument to `range` in some cases since the default +start position is 0. + +```python +# err +range(0, 10) + +# ok +range(10) +range(x, 10) +range(0, 10, x) +``` + +### PIE809: django-prefer-bulk + +Bulk create multiple objects instead of executing O(N) queries. + +```python +# error +[Item.objects.create(item) for item in items] + +# error +[Item.objects.create(item) for item in [bar for bar in buzz]] + +# error +(Item.objects.create(item) for item in items) + +# ok +Item.objects.insert(items) +Item.objects.create(item) +``` + +## development + +### examining the AST +You can use `astpretty` to dump the AST of a piece of code. + +```shell +./.venv/bin/astpretty <(pbpaste) +``` + +### uploading a new version to [PyPi](https://pypi.org) + +```shell +# increment `Flake8PieCheck.version` and pyproject.toml `version` + +# build new distribution files and upload to pypi +# Note: this will ask for login credentials +rm -rf dist && poetry publish --build +``` + + +%prep +%autosetup -n flake8-pie-0.16.0 + +%build +%py3_build + +%install +%py3_install +install -d -m755 %{buildroot}/%{_pkgdocdir} +if [ -d doc ]; then cp -arf doc %{buildroot}/%{_pkgdocdir}; fi +if [ -d docs ]; then cp -arf docs %{buildroot}/%{_pkgdocdir}; fi +if [ -d example ]; then cp -arf example %{buildroot}/%{_pkgdocdir}; fi +if [ -d examples ]; then cp -arf examples %{buildroot}/%{_pkgdocdir}; fi +pushd %{buildroot} +if [ -d usr/lib ]; then + find usr/lib -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/lib64 ]; then + find usr/lib64 -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/bin ]; then + find usr/bin -type f -printf "/%h/%f\n" >> filelist.lst +fi +if [ -d usr/sbin ]; then + find usr/sbin -type f -printf "/%h/%f\n" >> filelist.lst +fi +touch doclist.lst +if [ -d usr/share/man ]; then + find usr/share/man -type f -printf "/%h/%f.gz\n" >> doclist.lst +fi +popd +mv %{buildroot}/filelist.lst . +mv %{buildroot}/doclist.lst . + +%files -n python3-flake8-pie -f filelist.lst +%dir %{python3_sitelib}/* + +%files help -f doclist.lst +%{_docdir}/* + +%changelog +* Mon Apr 10 2023 Python_Bot - 0.16.0-1 +- Package Spec generated diff --git a/sources b/sources new file mode 100644 index 0000000..a660665 --- /dev/null +++ b/sources @@ -0,0 +1 @@ +1e85d8671cbe994dbe0d44422d2667d7 flake8-pie-0.16.0.tar.gz -- cgit v1.2.3