From 3e5432db47d58e631d17664bfe0ab17fdc18f1a7 Mon Sep 17 00:00:00 2001 From: Matthias Mair Date: Tue, 20 Aug 2024 00:03:39 +0200 Subject: [PATCH] Add RUF rules --- pyproject.toml | 6 +++++- src/backend/InvenTree/InvenTree/conversion.py | 3 ++- src/backend/InvenTree/InvenTree/format.py | 5 +++-- src/backend/InvenTree/InvenTree/helpers.py | 4 ++-- src/backend/InvenTree/InvenTree/models.py | 2 +- src/backend/InvenTree/InvenTree/serializers.py | 7 ++++--- src/backend/InvenTree/InvenTree/tasks.py | 8 +++++--- src/backend/InvenTree/common/forms.py | 6 +++--- src/backend/InvenTree/importer/models.py | 6 +++++- src/backend/InvenTree/machine/machine_type.py | 4 ++-- src/backend/InvenTree/machine/registry.py | 2 +- src/backend/InvenTree/order/serializers.py | 5 +++-- src/backend/InvenTree/part/bom.py | 7 ++++++- src/backend/InvenTree/plugin/base/barcodes/mixins.py | 8 +++++--- .../InvenTree/plugin/base/integration/APICallMixin.py | 5 +++-- .../InvenTree/plugin/base/integration/ValidationMixin.py | 6 +++++- src/backend/InvenTree/plugin/plugin.py | 3 ++- src/backend/InvenTree/stock/api.py | 2 +- src/backend/InvenTree/stock/models.py | 2 +- src/backend/InvenTree/stock/serializers.py | 2 +- src/backend/InvenTree/users/admin.py | 2 +- tasks.py | 3 ++- 22 files changed, 63 insertions(+), 35 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 04d9138b2a..d9beafa662 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,17 @@ src = ["src/backend/InvenTree"] "__init__.py" = ["D104"] [tool.ruff.lint] -select = ["A", "B", "C", "C4", "D", "F", "I", "N", "SIM", "PIE", "UP", "W"] +select = ["A", "B", "C", "C4", "D", "F", "I", "N", "SIM", "PIE", "RUF", "UP", "W"] # Things that should be enabled in the future: # - LOG # - DJ # for Django stuff # - S # for security stuff (bandit) ignore = [ + "RUF015", + # - RUF015 - Prefer next({iterable}) over single element slice + "RUF012", + # - RUF012 - Mutable class attributes should be annotated with typing.ClassVar "SIM117", # - SIM117 - Use a single with statement with multiple contexts instead of nested with statements "SIM102", diff --git a/src/backend/InvenTree/InvenTree/conversion.py b/src/backend/InvenTree/InvenTree/conversion.py index 968c939bc5..2e6c3fe866 100644 --- a/src/backend/InvenTree/InvenTree/conversion.py +++ b/src/backend/InvenTree/InvenTree/conversion.py @@ -2,6 +2,7 @@ import logging import re +from typing import Optional from django.core.exceptions import ValidationError from django.utils.translation import gettext_lazy as _ @@ -133,7 +134,7 @@ def convert_value(value, unit): return value -def convert_physical_value(value: str, unit: str = None, strip_units=True): +def convert_physical_value(value: str, unit: Optional[str] = None, strip_units=True): """Validate that the provided value is a valid physical quantity. Arguments: diff --git a/src/backend/InvenTree/InvenTree/format.py b/src/backend/InvenTree/InvenTree/format.py index edaff898ef..7d959f5596 100644 --- a/src/backend/InvenTree/InvenTree/format.py +++ b/src/backend/InvenTree/InvenTree/format.py @@ -2,6 +2,7 @@ import re import string +from typing import Optional from django.conf import settings from django.utils import translation @@ -179,8 +180,8 @@ def extract_named_group(name: str, value: str, fmt_string: str) -> str: def format_money( money: Money, - decimal_places: int = None, - format: str = None, + decimal_places: Optional[int] = None, + format: Optional[str] = None, include_symbol: bool = True, ) -> str: """Format money object according to the currently set local. diff --git a/src/backend/InvenTree/InvenTree/helpers.py b/src/backend/InvenTree/InvenTree/helpers.py index ef80d6e886..c8512d6168 100644 --- a/src/backend/InvenTree/InvenTree/helpers.py +++ b/src/backend/InvenTree/InvenTree/helpers.py @@ -9,7 +9,7 @@ import os.path import re from decimal import Decimal, InvalidOperation from pathlib import Path -from typing import TypeVar, Union +from typing import Optional, TypeVar, Union from wsgiref.util import FileWrapper from django.conf import settings @@ -859,7 +859,7 @@ def server_timezone() -> str: return settings.TIME_ZONE -def to_local_time(time, target_tz: str = None): +def to_local_time(time, target_tz: Optional[str] = None): """Convert the provided time object to the local timezone. Arguments: diff --git a/src/backend/InvenTree/InvenTree/models.py b/src/backend/InvenTree/InvenTree/models.py index 4c7dad2309..c57a0ccbcb 100644 --- a/src/backend/InvenTree/InvenTree/models.py +++ b/src/backend/InvenTree/InvenTree/models.py @@ -856,7 +856,7 @@ class InvenTreeTree(MetadataMixin, PluginValidationMixin, MPTTModel): Returns: List of category names from the top level to this category """ - return self.parentpath + [self] + return [*self.parentpath, self] def get_path(self): """Return a list of element in the item tree. diff --git a/src/backend/InvenTree/InvenTree/serializers.py b/src/backend/InvenTree/InvenTree/serializers.py index a86b1228e9..72fd0968cc 100644 --- a/src/backend/InvenTree/InvenTree/serializers.py +++ b/src/backend/InvenTree/InvenTree/serializers.py @@ -89,7 +89,7 @@ class InvenTreeCurrencySerializer(serializers.ChoiceField): ) if allow_blank: - choices = [('', '---------')] + choices + choices = [('', '---------'), *choices] kwargs['choices'] = choices @@ -424,14 +424,15 @@ class ExendedUserSerializer(UserSerializer): class Meta(UserSerializer.Meta): """Metaclass defines serializer fields.""" - fields = UserSerializer.Meta.fields + [ + fields = [ + *UserSerializer.Meta.fields, 'groups', 'is_staff', 'is_superuser', 'is_active', ] - read_only_fields = UserSerializer.Meta.read_only_fields + ['groups'] + read_only_fields = [*UserSerializer.Meta.read_only_fields, 'groups'] is_staff = serializers.BooleanField( label=_('Staff'), help_text=_('Does this user have staff permissions') diff --git a/src/backend/InvenTree/InvenTree/tasks.py b/src/backend/InvenTree/InvenTree/tasks.py index 479c624efc..76bcb2a3e3 100644 --- a/src/backend/InvenTree/InvenTree/tasks.py +++ b/src/backend/InvenTree/InvenTree/tasks.py @@ -9,7 +9,7 @@ import time import warnings from dataclasses import dataclass from datetime import datetime, timedelta -from typing import Callable +from typing import Callable, Optional from django.conf import settings from django.core.exceptions import AppRegistryNotReady @@ -291,7 +291,7 @@ class TaskRegister: task_list: list[ScheduledTask] = [] - def register(self, task, schedule, minutes: int = None): + def register(self, task, schedule, minutes: Optional[int] = None): """Register a task with the que.""" self.task_list.append(ScheduledTask(task, schedule, minutes)) @@ -299,7 +299,9 @@ class TaskRegister: tasks = TaskRegister() -def scheduled_task(interval: str, minutes: int = None, tasklist: TaskRegister = None): +def scheduled_task( + interval: str, minutes: Optional[int] = None, tasklist: TaskRegister = None +): """Register the given task as a scheduled task. Example: diff --git a/src/backend/InvenTree/common/forms.py b/src/backend/InvenTree/common/forms.py index ee20615c4e..85fd2f0732 100644 --- a/src/backend/InvenTree/common/forms.py +++ b/src/backend/InvenTree/common/forms.py @@ -65,7 +65,7 @@ class MatchFieldForm(forms.Form): for col in columns: field_name = col['name'] self.fields[field_name] = forms.ChoiceField( - choices=[('', '-' * 10)] + headers_choices, + choices=[('', '-' * 10), *headers_choices], required=False, widget=forms.Select(attrs={'class': 'select fieldselect'}), ) @@ -131,7 +131,7 @@ class MatchItemForm(forms.Form): item_match = row['match_' + col_guess] # Set field select box self.fields[field_name] = forms.ChoiceField( - choices=[('', '-' * 10)] + item_options, + choices=[('', '-' * 10), *item_options], required=False, widget=forms.Select(attrs={'class': 'select bomselect'}), ) @@ -151,7 +151,7 @@ class MatchItemForm(forms.Form): field_name = 'item_select-' + str(row['index']) # Set field select box self.fields[field_name] = forms.ChoiceField( - choices=[('', '-' * 10)] + item_options, + choices=[('', '-' * 10), *item_options], required=False, widget=forms.Select(attrs={'class': 'select bomselect'}), ) diff --git a/src/backend/InvenTree/importer/models.py b/src/backend/InvenTree/importer/models.py index 403861bec0..ab29c8ba32 100644 --- a/src/backend/InvenTree/importer/models.py +++ b/src/backend/InvenTree/importer/models.py @@ -2,6 +2,7 @@ import json import logging +from typing import Optional from django.contrib.auth.models import User from django.core.exceptions import ValidationError as DjangoValidationError @@ -537,7 +538,10 @@ class DataImportRow(models.Model): return overrides def extract_data( - self, available_fields: dict = None, field_mapping: dict = None, commit=True + self, + available_fields: Optional[dict] = None, + field_mapping: Optional[dict] = None, + commit=True, ): """Extract row data from the provided data dictionary.""" if not field_mapping: diff --git a/src/backend/InvenTree/machine/machine_type.py b/src/backend/InvenTree/machine/machine_type.py index 9d9a456e81..3239f5a149 100644 --- a/src/backend/InvenTree/machine/machine_type.py +++ b/src/backend/InvenTree/machine/machine_type.py @@ -139,7 +139,7 @@ class BaseDriver( Arguments: error: Exception or string """ - self.set_shared_state('errors', self.errors + [error]) + self.set_shared_state('errors', [*self.errors, error]) # --- state getters/setters @property @@ -317,7 +317,7 @@ class BaseMachineType( Arguments: error: Exception or string """ - self.set_shared_state('errors', self.errors + [error]) + self.set_shared_state('errors', [*self.errors, error]) def reset_errors(self): """Helper function for resetting the error list for a machine.""" diff --git a/src/backend/InvenTree/machine/registry.py b/src/backend/InvenTree/machine/registry.py index b9cdf1e0c9..be8aeb677a 100644 --- a/src/backend/InvenTree/machine/registry.py +++ b/src/backend/InvenTree/machine/registry.py @@ -38,7 +38,7 @@ class MachineRegistry( def handle_error(self, error: Union[Exception, str]): """Helper function for capturing errors with the machine registry.""" - self.set_shared_state('errors', self.errors + [error]) + self.set_shared_state('errors', [*self.errors, error]) def initialize(self, main: bool = False): """Initialize the machine registry.""" diff --git a/src/backend/InvenTree/order/serializers.py b/src/backend/InvenTree/order/serializers.py index e9770f244f..7669556c53 100644 --- a/src/backend/InvenTree/order/serializers.py +++ b/src/backend/InvenTree/order/serializers.py @@ -164,7 +164,8 @@ class AbstractOrderSerializer(DataImportExportSerializerMixin, serializers.Seria 'notes', 'barcode_hash', 'overdue', - ] + extra_fields + *extra_fields, + ] class AbstractLineItemSerializer: @@ -433,7 +434,7 @@ class PurchaseOrderLineItemSerializer( def skip_create_fields(self): """Return a list of fields to skip when creating a new object.""" - return ['auto_pricing', 'merge_items'] + super().skip_create_fields() + return ['auto_pricing', 'merge_items', *super().skip_create_fields()] @staticmethod def annotate_queryset(queryset): diff --git a/src/backend/InvenTree/part/bom.py b/src/backend/InvenTree/part/bom.py index 580347f8a9..58359e89df 100644 --- a/src/backend/InvenTree/part/bom.py +++ b/src/backend/InvenTree/part/bom.py @@ -4,6 +4,7 @@ Primarily BOM upload tools. """ from collections import OrderedDict +from typing import Optional from django.utils.translation import gettext as _ @@ -40,7 +41,11 @@ def MakeBomTemplate(fmt): def ExportBom( - part: Part, fmt='csv', cascade: bool = False, max_levels: int = None, **kwargs + part: Part, + fmt='csv', + cascade: bool = False, + max_levels: Optional[int] = None, + **kwargs, ): """Export a BOM (Bill of Materials) for a given part. diff --git a/src/backend/InvenTree/plugin/base/barcodes/mixins.py b/src/backend/InvenTree/plugin/base/barcodes/mixins.py index 24f96530a3..1d53a027e6 100644 --- a/src/backend/InvenTree/plugin/base/barcodes/mixins.py +++ b/src/backend/InvenTree/plugin/base/barcodes/mixins.py @@ -384,7 +384,9 @@ class SupplierBarcodeMixin(BarcodeMixin): return orders_intersection if orders_intersection else orders_union @staticmethod - def get_supplier_parts(sku: str = None, supplier: Company = None, mpn: str = None): + def get_supplier_parts( + sku: str | None = None, supplier: Company = None, mpn: str | None = None + ): """Get a supplier part from SKU or by supplier and MPN.""" if not (sku or supplier or mpn): return SupplierPart.objects.none() @@ -420,10 +422,10 @@ class SupplierBarcodeMixin(BarcodeMixin): def receive_purchase_order_item( supplier_part: SupplierPart, user: User, - quantity: Decimal | str = None, + quantity: Decimal | str | None = None, purchase_order: PurchaseOrder = None, location: StockLocation = None, - barcode: str = None, + barcode: str | None = None, ) -> dict: """Try to receive a purchase order item. diff --git a/src/backend/InvenTree/plugin/base/integration/APICallMixin.py b/src/backend/InvenTree/plugin/base/integration/APICallMixin.py index 77131f2491..bb9ac1538f 100644 --- a/src/backend/InvenTree/plugin/base/integration/APICallMixin.py +++ b/src/backend/InvenTree/plugin/base/integration/APICallMixin.py @@ -3,6 +3,7 @@ import json as json_pkg import logging from collections.abc import Iterable +from typing import Optional import requests @@ -117,10 +118,10 @@ class APICallMixin: self, endpoint: str, method: str = 'GET', - url_args: dict = None, + url_args: Optional[dict] = None, data=None, json=None, - headers: dict = None, + headers: Optional[dict] = None, simple_response: bool = True, endpoint_is_url: bool = False, ): diff --git a/src/backend/InvenTree/plugin/base/integration/ValidationMixin.py b/src/backend/InvenTree/plugin/base/integration/ValidationMixin.py index c8508ed4b5..36bdb74271 100644 --- a/src/backend/InvenTree/plugin/base/integration/ValidationMixin.py +++ b/src/backend/InvenTree/plugin/base/integration/ValidationMixin.py @@ -1,5 +1,7 @@ """Validation mixin class definition.""" +from typing import Optional + from django.core.exceptions import ValidationError from django.db.models import Model @@ -67,7 +69,9 @@ class ValidationMixin: """ return None - def validate_model_instance(self, instance: Model, deltas: dict = None) -> None: + def validate_model_instance( + self, instance: Model, deltas: Optional[dict] = None + ) -> None: """Run custom validation on a database model instance. This method is called when a model instance is being validated. diff --git a/src/backend/InvenTree/plugin/plugin.py b/src/backend/InvenTree/plugin/plugin.py index 6cafd510c0..5f16bac369 100644 --- a/src/backend/InvenTree/plugin/plugin.py +++ b/src/backend/InvenTree/plugin/plugin.py @@ -7,6 +7,7 @@ from datetime import datetime from distutils.sysconfig import get_python_lib from importlib.metadata import PackageNotFoundError, metadata from pathlib import Path +from typing import Optional from django.conf import settings from django.urls.base import reverse @@ -27,7 +28,7 @@ class MetaBase: SLUG = None TITLE = None - def get_meta_value(self, key: str, old_key: str = None, __default=None): + def get_meta_value(self, key: str, old_key: Optional[str] = None, __default=None): """Reference a meta item with a key. Args: diff --git a/src/backend/InvenTree/stock/api.py b/src/backend/InvenTree/stock/api.py index e05064a43f..d2226f529e 100644 --- a/src/backend/InvenTree/stock/api.py +++ b/src/backend/InvenTree/stock/api.py @@ -1025,7 +1025,7 @@ class StockList(DataExportViewMixin, ListCreateDestroyAPIView): msg += ' : ' msg += ','.join([str(e) for e in invalid]) - raise ValidationError({'serial_numbers': errors + [msg]}) + raise ValidationError({'serial_numbers': [*errors, msg]}) except DjangoValidationError as e: raise ValidationError({ diff --git a/src/backend/InvenTree/stock/models.py b/src/backend/InvenTree/stock/models.py index 4edffbf4c6..09708a050f 100644 --- a/src/backend/InvenTree/stock/models.py +++ b/src/backend/InvenTree/stock/models.py @@ -1432,7 +1432,7 @@ class StockItem( self, entry_type: int, user: User, - deltas: dict = None, + deltas: dict | None = None, notes: str = '', **kwargs, ): diff --git a/src/backend/InvenTree/stock/serializers.py b/src/backend/InvenTree/stock/serializers.py index 88717f9916..f4f841e542 100644 --- a/src/backend/InvenTree/stock/serializers.py +++ b/src/backend/InvenTree/stock/serializers.py @@ -1519,7 +1519,7 @@ def stock_item_adjust_status_options(): In particular, include a Null option for the status field. """ - return [(None, _('No Change'))] + stock.status_codes.StockStatus.items() + return [(None, _('No Change')), *stock.status_codes.StockStatus.items()] class StockAdjustmentItemSerializer(serializers.Serializer): diff --git a/src/backend/InvenTree/users/admin.py b/src/backend/InvenTree/users/admin.py index 0d06dc664d..9a15b1e4c3 100644 --- a/src/backend/InvenTree/users/admin.py +++ b/src/backend/InvenTree/users/admin.py @@ -63,7 +63,7 @@ class RuleSetInline(admin.TabularInline): can_delete = False verbose_name = 'Ruleset' verbose_plural_name = 'Rulesets' - fields = ['name'] + list(RuleSet.RULE_OPTIONS) + fields = ['name', *list(RuleSet.RULE_OPTIONS)] readonly_fields = ['name'] max_num = len(RuleSet.RULESET_CHOICES) min_num = 1 diff --git a/tasks.py b/tasks.py index a5fbf9ab28..98becb93e3 100644 --- a/tasks.py +++ b/tasks.py @@ -9,6 +9,7 @@ import subprocess import sys from pathlib import Path from platform import python_version +from typing import Optional from invoke import task @@ -135,7 +136,7 @@ def managePyPath(): return managePyDir().joinpath('manage.py') -def run(c, cmd, path: Path = None, pty=False, env=None): +def run(c, cmd, path: Optional[Path] = None, pty=False, env=None): """Runs a given command a given path. Args: