2
0
mirror of https://github.com/inventree/InvenTree.git synced 2026-03-21 11:44:42 +00:00

chore(backend): Bump ty (#11537)

* bump ty - there is better django support now

* more fixes

* fix usage of types

* add missing type

* fix access

* ensure itteration is safe

* fix uncombat decimal usage

* ?potential breaking: change access key

* remove now obsolete igtnore

* ignore errors on StdImageField

* remove usage of unkonw parameter

* fix diff error

* fix schema creation

* fix coverage quirk

* those are unneeded now?

* this seem to have been an issue with 3.12; not occuring on 3.14

* ignore pydantiics

* ignore edge cases for now

* include isGenerating fix

* make typing python 3.11 compatible

* fix more errors
This commit is contained in:
Matthias Mair
2026-03-18 08:25:50 +01:00
committed by GitHub
parent 865ec47a3b
commit 16103379c9
40 changed files with 114 additions and 96 deletions

View File

@@ -108,13 +108,13 @@ root = ["src/backend/InvenTree"]
unresolved-reference="ignore" # 21 # see https://github.com/astral-sh/ty/issues/220
unresolved-attribute="ignore" # 505 # need Plugin Mixin typing
call-non-callable="ignore" # 8 ##
invalid-argument-type="ignore" # 49
invalid-assignment="ignore" # 17 # need to wait for better django field stubs
invalid-method-override="ignore"
invalid-method-override="ignore" # 99
invalid-return-type="ignore" # 22 ##
possibly-missing-attribute="ignore" # 25 # https://github.com/astral-sh/ty/issues/164
unknown-argument="ignore" # 3 # need to wait for better django field stubs
no-matching-overload="ignore" # 3 # need to wait for better django field stubs
invalid-argument-type="ignore" # 49
no-matching-overload="ignore" # 3 # need to wait for betterdjango field stubs
[tool.coverage.run]
source = ["src/backend/InvenTree", "InvenTree"]

View File

@@ -199,7 +199,7 @@ def regenerate_imagefile(_file, _name: str):
_name: Name of the variation (e.g. 'thumbnail', 'preview')
"""
name = _file.field.attr_class.get_variation_name(_file.name, _name)
return ImageFieldFile(_file.instance, _file, name) # type: ignore
return ImageFieldFile(_file.instance, _file, name) # ty:ignore[too-many-positional-arguments]
def image2name(img_obj: StdImageField, do_preview: bool, do_thumbnail: bool):
@@ -311,7 +311,7 @@ def TestIfImageURL(url):
]
def str2bool(text, test=True):
def str2bool(text, test=True) -> bool:
"""Test if a string 'looks' like a boolean value.
Args:

View File

@@ -8,6 +8,7 @@ import structlog
from allauth.account.models import EmailAddress
import InvenTree.ready
import InvenTree.tasks as tasks
from common.models import Priority, issue_mail
logger = structlog.get_logger('inventree')
@@ -98,7 +99,7 @@ def send_email(
)
return False, 'INVE-W7: no from_email or DEFAULT_FROM_EMAIL specified'
InvenTree.tasks.offload_task(
tasks.offload_task(
issue_mail,
subject=subject,
body=body,

View File

@@ -99,7 +99,7 @@ class Command(BaseCommand):
if kwargs['include_items']:
icons[icon]['items'].append({
'model': model.__name__.lower(),
'id': item.id, # type: ignore
'id': item.id,
})
self.stdout.write(f'Writing icon map for {len(icons.keys())} icons')

View File

@@ -1329,7 +1329,7 @@ class InvenTreeBarcodeMixin(models.Model):
@classmethod
def lookup_barcode(cls, barcode_hash: str) -> models.Model:
"""Check if a model instance exists with the specified third-party barcode hash."""
return cls.objects.filter(barcode_hash=barcode_hash).first()
return cls.objects.filter(barcode_hash=barcode_hash).first() # ty:ignore[invalid-return-type]
def assign_barcode(
self,
@@ -1485,7 +1485,7 @@ class InvenTreeImageMixin(models.Model):
def rename_image(self, filename):
"""Rename the uploaded image file using the IMAGE_RENAME function."""
return self.IMAGE_RENAME(filename) # type: ignore
return self.IMAGE_RENAME(filename)
image = StdImageField(
upload_to=rename_image,

View File

@@ -376,7 +376,7 @@ def auth_exempt(view_func):
def wrapped_view(*args, **kwargs):
return view_func(*args, **kwargs)
wrapped_view.auth_exempt = True # type:ignore[unresolved-attribute]
wrapped_view.auth_exempt = True
return wraps(view_func)(wrapped_view)

View File

@@ -483,7 +483,7 @@ if LDAP_AUTH: # pragma: no cover
)
AUTH_LDAP_USER_SEARCH = django_auth_ldap.config.LDAPSearch(
get_setting('INVENTREE_LDAP_SEARCH_BASE_DN', 'ldap.search_base_dn'),
ldap.SCOPE_SUBTREE, # type: ignore[unresolved-attribute]
ldap.SCOPE_SUBTREE,
str(
get_setting(
'INVENTREE_LDAP_SEARCH_FILTER_STR',
@@ -519,7 +519,7 @@ if LDAP_AUTH: # pragma: no cover
)
AUTH_LDAP_GROUP_SEARCH = django_auth_ldap.config.LDAPSearch(
get_setting('INVENTREE_LDAP_GROUP_SEARCH', 'ldap.group_search'),
ldap.SCOPE_SUBTREE, # type: ignore[unresolved-attribute]
ldap.SCOPE_SUBTREE,
f'(objectClass={AUTH_LDAP_GROUP_OBJECT_CLASS})',
)
AUTH_LDAP_GROUP_TYPE_CLASS = get_setting(

View File

@@ -49,7 +49,7 @@ def check_provider(provider):
if not app:
return False
if allauth.app_settings.SITES_ENABLED: # type: ignore[unresolved-attribute]
if allauth.app_settings.SITES_ENABLED:
# At least one matching site must be specified
if not app.sites.exists():
logger.error('SocialApp %s has no sites configured', app)

View File

@@ -330,7 +330,7 @@ class ScheduledTask:
QUARTERLY: str = 'Q'
YEARLY: str = 'Y'
TYPE: tuple[str] = (MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY) # type: ignore[invalid-assignment]
TYPE: tuple[str] = (MINUTES, HOURLY, DAILY, WEEKLY, MONTHLY, QUARTERLY, YEARLY)
class TaskRegister:

View File

@@ -574,7 +574,7 @@ class GeneralApiTests(InvenTreeAPITestCase):
self.assertIn('License file not found at', str(log.output))
with TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
with TemporaryDirectory() as tmp:
sample_file = Path(tmp, 'temp.txt')
sample_file.write_text('abc', 'utf-8')

View File

@@ -703,12 +703,12 @@ class TestHelpers(TestCase):
"""Test getMediaUrl."""
# Str should not work
with self.assertRaises(TypeError):
helpers.getMediaUrl('xx/yy.png') # type: ignore
helpers.getMediaUrl('xx/yy.png')
# Correct usage
part = Part().image
self.assertEqual(
helpers.getMediaUrl(StdImageFieldFile(part, part, 'xx/yy.png')), # type: ignore
helpers.getMediaUrl(StdImageFieldFile(part, part, 'xx/yy.png')), # ty:ignore[too-many-positional-arguments]
'/media/xx/yy.png',
)

View File

@@ -4,7 +4,7 @@ import base64
import logging
from typing import Optional
from opentelemetry import metrics, trace # type: ignore[import]
from opentelemetry import metrics, trace
from opentelemetry.instrumentation.django import DjangoInstrumentor
from opentelemetry.instrumentation.redis import RedisInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

View File

@@ -289,8 +289,10 @@ def inventreeBranch():
return ' '.join(branch.splitlines())
if main_branch is None:
return None
return main_branch.decode('utf-8')
return None # pragma: no cover - branch information may not be available in all environments
return main_branch.decode(
'utf-8'
) # pragma: no cover - branch information may not be available in all environments
def inventreeTarget():

View File

@@ -17,6 +17,7 @@ from rest_framework.response import Response
import build.models as build_models
import build.serializers
import common.models
import common.serializers
import part.models as part_models
import stock.models as stock_models
import stock.serializers

View File

@@ -1,7 +1,7 @@
"""Build database model definitions."""
import decimal
from typing import Optional
from typing import Optional, TypedDict
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
@@ -50,7 +50,7 @@ from stock.status_codes import StockHistoryCode, StockStatus
logger = structlog.get_logger('inventree')
class BuildReportContext(report.mixins.BaseReportContext):
class BuildReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the Build model.
Attributes:
@@ -1039,7 +1039,7 @@ class Build(
lines = lines.exclude(bom_item__consumable=True)
lines = lines.annotate(allocated=annotate_allocated_quantity())
for build_line in lines: # type: ignore[non-iterable]
for build_line in lines:
reduce_by = build_line.allocated - build_line.quantity
if reduce_by <= 0:
@@ -1512,10 +1512,10 @@ class Build(
unallocated_quantity -= quantity
except (ValidationError, serializers.ValidationError) as exc:
# Catch model errors and re-throw as DRF errors
# Re-raise with a Django-compatible validation payload
raise ValidationError(
exc.message, detail=serializers.as_serializer_error(exc)
)
serializers.as_serializer_error(exc)
) from exc
if unallocated_quantity <= 0:
# We have now fully-allocated this BomItem - no need to continue!
@@ -1695,7 +1695,7 @@ def after_save_build(sender, instance: Build, created: bool, **kwargs):
instance.update_build_line_items()
class BuildLineReportContext(report.mixins.BaseReportContext):
class BuildLineReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the BuildLine model.
Attributes:
@@ -1745,6 +1745,7 @@ class BuildLine(report.mixins.InvenTreeReportMixin, InvenTree.models.InvenTreeMo
"""Return the API URL used to access this model."""
return reverse('api-build-line-list')
# type
def report_context(self) -> BuildLineReportContext:
"""Generate custom report context for this BuildLine object."""
return {

View File

@@ -230,6 +230,6 @@ def get_price(
quantity = decimal.Decimal(f'{quantity}')
if pb_found:
cost = pb_cost * quantity
cost = decimal.Decimal(pb_cost) * quantity
return InvenTree.helpers.normalize(cost + instance.base_cost)
return None

View File

@@ -85,8 +85,8 @@ class RenderMeta(enums.ChoicesType):
return []
class RenderChoices(models.TextChoices, metaclass=RenderMeta): # type: ignore
"""Class for creating enumerated string choices for schema rendering."""
class RenderChoices(models.TextChoices, metaclass=RenderMeta):
"""Class for creating enumerated string choices for schema rendering.""" # ty:ignore[conflicting-metaclass]
class MetaMixin(models.Model):
@@ -1084,7 +1084,7 @@ class BaseInvenTreeSetting(models.Model):
return self.__class__.validator_is_bool(validator)
def as_bool(self):
def as_bool(self) -> bool:
"""Return the value of this setting converted to a boolean value.
Warning: Only use on values where is_bool evaluates to true!

View File

@@ -42,7 +42,7 @@ class SettingsValueField(serializers.Field):
"""Return the object instance, not the attribute value."""
return instance
def to_representation(self, instance: common_models.InvenTreeSetting) -> str:
def to_representation(self, instance: common_models.InvenTreeSetting):
"""Return the value of the setting.
Protected settings are returned as '***'
@@ -381,7 +381,7 @@ class ConfigSerializer(serializers.Serializer):
"""Return the configuration data as a dictionary."""
if not isinstance(instance, str):
instance = list(instance.keys())[0]
return {'key': instance, **self.instance[instance]}
return {'key': instance, **self.instance.get(instance)}
class NotesImageSerializer(InvenTreeModelSerializer):
@@ -457,7 +457,7 @@ class FlagSerializer(serializers.Serializer):
data = {'key': instance, 'state': flag_state(instance, request=request)}
if request and request.user.is_superuser:
data['conditions'] = self.instance[instance]
data['conditions'] = self.instance.get(instance)
return data

View File

@@ -26,6 +26,7 @@ class SettingsKeyType(TypedDict, total=False):
validator: Validation function/list of functions for the setting (optional, default: None, e.g: bool, int, str, MinValueValidator, ...)
default: Default value or function that returns default value (optional)
choices: Function that returns or value of list[tuple[str: key, str: display value]] (optional)
model_filters: Filters to apply when querying the associated model (optional)
hidden: Hide this setting from settings page (optional)
before_save: Function that gets called after save with *args, **kwargs (optional)
after_save: Function that gets called after save with *args, **kwargs (optional)
@@ -42,6 +43,7 @@ class SettingsKeyType(TypedDict, total=False):
validator: Callable | list[Callable] | tuple[Callable]
default: Callable | Any
choices: list[tuple[str, str]] | Callable[[], list[tuple[str, str]]]
model_filters: dict[str, Any]
hidden: bool
before_save: Callable[..., None]
after_save: Callable[..., None]

View File

@@ -2,6 +2,7 @@
import os
from decimal import Decimal
from typing import TypedDict
from django.apps import apps
from django.conf import settings
@@ -53,7 +54,7 @@ def rename_company_image(instance, filename):
return os.path.join(base, fn)
class CompanyReportContext(report.mixins.BaseReportContext):
class CompanyReportContext(report.mixins.BaseReportContext, TypedDict):
"""Report context for the Company model.
Attributes:
@@ -243,7 +244,11 @@ class Company(
# We may have a pre-fetched primary address list
if hasattr(self, 'primary_address_list'):
addresses = self.primary_address_list
return addresses[0] if len(addresses) > 0 else None
return (
addresses[0]
if len(addresses) > 0 and isinstance(addresses, list)
else None
)
# Otherwise, query the database
return self.addresses.filter(primary=True).first()

View File

@@ -194,7 +194,7 @@ class InvenTreeCustomStatusSerializerMixin:
"""Ensure the custom field is updated if the leader was changed."""
self.gather_custom_fields()
# Mirror values from leader to follower
for field in self._custom_fields_leader:
for field in self._custom_fields_leader or []:
follower_field_name = f'{field}_custom_key'
if (
field in self.initial_data
@@ -205,7 +205,7 @@ class InvenTreeCustomStatusSerializerMixin:
setattr(self.instance, follower_field_name, self.initial_data[field])
# Mirror values from follower to leader
for field in self._custom_fields_follower:
for field in self._custom_fields_follower or []:
leader_field_name = field.replace('_custom_key', '')
if field in validated_data and leader_field_name not in self.initial_data:
try:
@@ -276,7 +276,7 @@ class InvenTreeCustomStatusSerializerMixin:
# Inherit choices from leader
self.gather_custom_fields()
if field_name in self._custom_fields:
if self._custom_fields and field_name in self._custom_fields:
leader_field_name = field_name.replace('_custom_key', '')
leader_field = self.fields[leader_field_name]
if hasattr(leader_field, 'choices'):

View File

@@ -152,7 +152,7 @@ class DataImportSession(models.Model):
return supported_models().get(self.model_type, None)
def get_related_model(self, field_name: str) -> models.Model:
def get_related_model(self, field_name: str) -> Optional[models.Model]:
"""Return the related model for a given field name.
Arguments:
@@ -699,7 +699,7 @@ class DataImportRow(models.Model):
if commit:
self.save()
def convert_date_field(self, value: str) -> str:
def convert_date_field(self, value: str) -> Optional[str]:
"""Convert an incoming date field to the correct format for the database."""
if value in [None, '']:
return None

View File

@@ -111,7 +111,7 @@ class LabelPrinterBaseDriver(BaseDriver):
Returns:
A class instance of a DRF serializer class, by default this an instance of self.PrintingOptionsSerializer using the *args, **kwargs if existing for this driver
"""
return self.PrintingOptionsSerializer(*args, **kwargs) # type: ignore
return self.PrintingOptionsSerializer(*args, **kwargs)
# --- helper functions
@property

View File

@@ -138,7 +138,7 @@ class MachineSettingSerializer(GenericReferencedSettingSerializer):
"""Custom init method to make the config_type field read only."""
super().__init__(*args, **kwargs)
self.Meta.read_only_fields = ['config_type'] # type: ignore
self.Meta.read_only_fields = ['config_type']
class BaseMachineClassSerializer(serializers.Serializer):

View File

@@ -232,10 +232,10 @@ class TestLabelPrinterMachineType(InvenTreeAPITestCase):
machine = self.create_machine()
# setup the label app
apps.get_app_config('report').create_default_labels() # type: ignore
apps.get_app_config('report').create_default_labels()
plg_registry.reload_plugins()
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config()) # type: ignore
config = cast(PluginConfig, plg_registry.get_plugin(plugin_ref).plugin_config())
config.active = True
config.save()

View File

@@ -1,7 +1,7 @@
"""Order model definitions."""
from decimal import Decimal
from typing import Any, Optional
from typing import Any, Optional, TypedDict
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
@@ -179,7 +179,7 @@ class TotalPriceMixin(models.Model):
return total
class BaseOrderReportContext(report.mixins.BaseReportContext):
class BaseOrderReportContext(report.mixins.BaseReportContext, TypedDict):
"""Base context for all order models.
Attributes:
@@ -199,7 +199,7 @@ class BaseOrderReportContext(report.mixins.BaseReportContext):
title: str
class PurchaseOrderReportContext(report.mixins.BaseReportContext):
class PurchaseOrderReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the purchase order model.
Attributes:
@@ -221,7 +221,7 @@ class PurchaseOrderReportContext(report.mixins.BaseReportContext):
supplier: Optional[Company]
class SalesOrderReportContext(report.mixins.BaseReportContext):
class SalesOrderReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the sales order model.
Attributes:
@@ -243,7 +243,7 @@ class SalesOrderReportContext(report.mixins.BaseReportContext):
customer: Optional[Company]
class ReturnOrderReportContext(report.mixins.BaseReportContext):
class ReturnOrderReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the return order model.
Attributes:
@@ -570,7 +570,9 @@ class PurchaseOrder(TotalPriceMixin, Order):
def report_context(self) -> PurchaseOrderReportContext:
"""Return report context data for this PurchaseOrder."""
return {**super().report_context(), 'supplier': self.supplier}
return_ctx = super().report_context()
return_ctx.update({'supplier': self.supplier})
return return_ctx
def get_absolute_url(self) -> str:
"""Get the 'web' URL for this order."""
@@ -1269,7 +1271,9 @@ class SalesOrder(TotalPriceMixin, Order):
def report_context(self) -> SalesOrderReportContext:
"""Generate report context data for this SalesOrder."""
return {**super().report_context(), 'customer': self.customer}
return_ctx = super().report_context()
return_ctx.update({'customer': self.customer})
return return_ctx
def get_absolute_url(self) -> str:
"""Get the 'web' URL for this order."""
@@ -2197,7 +2201,7 @@ class SalesOrderLineItem(OrderLineItem):
return self.shipped >= self.quantity
class SalesOrderShipmentReportContext(report.mixins.BaseReportContext):
class SalesOrderShipmentReportContext(report.mixins.BaseReportContext, TypedDict):
"""Context for the SalesOrderShipment model.
Attributes:
@@ -2662,7 +2666,9 @@ class ReturnOrder(TotalPriceMixin, Order):
def report_context(self) -> ReturnOrderReportContext:
"""Generate report context data for this ReturnOrder."""
return {**super().report_context(), 'customer': self.customer}
return_ctx = super().report_context()
return_ctx.update({'customer': self.customer})
return return_ctx
def get_absolute_url(self):
"""Get the 'web' URL for this order."""

View File

@@ -10,7 +10,7 @@ import os
import re
from datetime import timedelta
from decimal import Decimal, InvalidOperation
from typing import cast
from typing import TypedDict, cast
from django.conf import settings
from django.contrib.auth.models import User
@@ -427,7 +427,7 @@ class PartCategoryParameterTemplate(InvenTree.models.InvenTreeMetadataModel):
)
class PartReportContext(report.mixins.BaseReportContext):
class PartReportContext(report.mixins.BaseReportContext, TypedDict):
"""Report context for the Part model.
Attributes:
@@ -4216,7 +4216,7 @@ class BomItem(InvenTree.models.MetadataMixin, InvenTree.models.InvenTreeModel):
if n <= 0:
return 0.0
return int(available_stock / n)
return int(Decimal(available_stock) / n)
def get_required_quantity(self, build_quantity: float) -> float:
"""Calculate the required part quantity, based on the supplied build_quantity.

View File

@@ -12,6 +12,7 @@ from opentelemetry import trace
import common.currency
import common.notifications
import InvenTree.helpers
import InvenTree.helpers_model
from common.settings import get_global_setting
from InvenTree.tasks import (

View File

@@ -50,8 +50,8 @@ class StaleStockNotificationTests(InvenTreeTestCase):
set_global_setting('STOCK_STALE_DAYS', 7, self.user)
# Clear notifications
NotificationEntry.objects.all().delete() # type: ignore[attr-defined]
NotificationMessage.objects.all().delete() # type: ignore[attr-defined]
NotificationEntry.objects.all().delete()
NotificationMessage.objects.all().delete()
def create_stock_items_with_expiry(self):
"""Create stock items with various expiry dates for testing."""
@@ -101,7 +101,7 @@ class StaleStockNotificationTests(InvenTreeTestCase):
part.tasks.notify_stale_stock(self.user, [])
# No notifications should be created
self.assertEqual(NotificationMessage.objects.count(), 0) # type: ignore[attr-defined]
self.assertEqual(NotificationMessage.objects.count(), 0)
def test_notify_stale_stock_single_item(self):
"""Test notify_stale_stock with a single stale item."""

View File

@@ -60,7 +60,7 @@ class BarcodeMixin:
"""Does this plugin support barcode generation."""
try:
# Attempt to call the generate method
self.generate(None) # type: ignore
self.generate(None)
except NotImplementedError:
# If a NotImplementedError is raised, then barcode generation is not supported
return False

View File

@@ -161,7 +161,7 @@ class SupplierMixin(SettingsMixin, Generic[PartData]):
# assign parent_part to root_part if root_part has no variant of already
if root_part and not root_part.is_template and not root_part.variant_of:
root_part.variant_of = parent_part # type: ignore
root_part.variant_of = parent_part
root_part.save()
return parent_part

View File

@@ -83,7 +83,7 @@ class UserInterfaceMixin:
def __init__(self):
"""Register mixin."""
super().__init__()
self.add_mixin(PluginMixinEnum.USER_INTERFACE, True, __class__) # type: ignore
self.add_mixin(PluginMixinEnum.USER_INTERFACE, True, __class__)
def get_ui_features(
self, feature_type: FeatureType, context: dict, request: Request, **kwargs

View File

@@ -7,4 +7,4 @@ class BrokenFileIntegrationPlugin(InvenTreePlugin):
"""An very broken plugin."""
aaa = bb # noqa: F821 # type: ignore[unresolved-reference]
aaa = bb # noqa: F821

View File

@@ -192,7 +192,7 @@ def get_modules(pkg, path=None):
if sys.version_info < (3, 12):
module = finder.find_module(name).load_module(name)
else:
spec = finder.find_spec(name)
spec = finder.find_spec(name) # type: ignore[missing-argument]
module = module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)

View File

@@ -3,7 +3,7 @@
import inspect
import warnings
from datetime import datetime
from distutils.sysconfig import get_python_lib # type: ignore[import]
from distutils.sysconfig import get_python_lib # type: ignore[unresolved-import]
from importlib.metadata import PackageNotFoundError, metadata
from pathlib import Path
from typing import Optional
@@ -92,16 +92,16 @@ class MetaBase:
TITLE = None
@mark_final
def get_meta_value(self, key: str, old_key: Optional[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:
key (str): key for the value
old_key (str, optional): deprecated key - will throw warning
__default (optional): Value if nothing with key can be found. Defaults to None.
default (optional): Value if nothing with key can be found. Defaults to None.
Returns:
Value referenced with key, old_key or __default if set and not value found
Value referenced with key, old_key or default if set and not value found
"""
value = getattr(self, key, None)
@@ -117,9 +117,9 @@ class MetaBase:
stacklevel=2,
)
# Use __default if still nothing set
if (value is None) and __default:
return __default
# Use default if still nothing set
if value is None and default is not None:
return default
return value
@mark_final

View File

@@ -781,9 +781,9 @@ class PluginsRegistry:
f"Plugin '{p}' is not compatible with the current InvenTree version {v}"
)
if v := plg_i.MIN_VERSION:
_msg += _(f'Plugin requires at least version {v}') # type: ignore[unsupported-operator]
_msg += _(f'Plugin requires at least version {v}') # ty:ignore[unsupported-operator]
if v := plg_i.MAX_VERSION:
_msg += _(f'Plugin requires at most version {v}') # type: ignore[unsupported-operator]
_msg += _(f'Plugin requires at most version {v}') # ty:ignore[unsupported-operator]
# Log to error stack
log_registry_error(_msg, reference=f'{p}:init_plugin')
else:

View File

@@ -152,7 +152,7 @@ class SampleSupplierPlugin(SupplierMixin, InvenTreePlugin):
# after the template part was created, we need to refresh the part from the db because its tree id may have changed
# which results in an error if saved directly
part.refresh_from_db()
part.variant_of = parent_part # type: ignore
part.variant_of = parent_part
part.save()
return part

View File

@@ -254,7 +254,7 @@ class RegistryTests(TestQueryMixin, PluginRegistryMixin, TestCase):
def test_folder_loading(self):
"""Test that plugins in folders outside of BASE_DIR get loaded."""
# Run in temporary directory -> always a new random name
with tempfile.TemporaryDirectory() as tmp: # type: ignore[no-matching-overload]
with tempfile.TemporaryDirectory() as tmp:
# Fill directory with sample data
new_dir = Path(tmp).joinpath('mock')
shutil.copytree(self.mockDir(), new_dir)

View File

@@ -704,7 +704,7 @@ class LabelTemplate(TemplateUploadMixin, ReportTemplateBase):
def get_context(self, instance, request=None, **kwargs):
"""Supply context data to the label template for rendering."""
base_context = super().get_context(instance, request, **kwargs)
label_context: LabelContextExtension = { # type: ignore[invalid-assignment]
label_context: LabelContextExtension = {
'width': self.width,
'height': self.height,
'page_style': None,

View File

@@ -710,25 +710,24 @@ tomli==2.4.0 \
--hash=sha256:d3d1654e11d724760cdb37a3d7691f0be9db5fbdaef59c9f532aabf87006dbaa \
--hash=sha256:d878f2a6707cc9d53a1be1414bbb419e629c3d6e67f69230217bb663e76b5087
# via coverage
ty==0.0.1a21 \
--hash=sha256:0efba2e52b58f536f4198ba5c4a36cac2ba67d83ec6f429ebc7704233bcda4c3 \
--hash=sha256:1474d883129bb63da3b2380fc7ead824cd3baf6a9551e6aa476ffefc58057af3 \
--hash=sha256:1f276ceab23a1410aec09508248c76ae0989c67fb7a0c287e0d4564994295531 \
--hash=sha256:218d53e7919e885bd98e9196d9cb952d82178b299aa36da6f7f39333eb7400ed \
--hash=sha256:21f708d02b6588323ffdbfdba38830dd0ecfd626db50aa6006b296b5470e52f9 \
--hash=sha256:334d2a212ebf42a0e55d57561926af7679fe1e878175e11dcb81ad8df892844e \
--hash=sha256:3c3bc66fcae41eff133cfe326dd65d82567a2fb5d4efe2128773b10ec2766819 \
--hash=sha256:5dfc73299d441cc6454e36ed0a976877415024143dfca6592dc36f7701424383 \
--hash=sha256:7505aeb8bf2a62f00f12cfa496f6c965074d75c8126268776565284c8a12d5dd \
--hash=sha256:84243455f295ed850bd53f7089819321807d4e6ee3b1cbff6086137ae0259466 \
--hash=sha256:87a200c21e02962e8a27374d9d152582331d57d709672431be58f4f898bf6cad \
--hash=sha256:9463cac96b8f1bb5ba740fe1d42cd6bd152b43c5b159b2f07f8fd629bcdded34 \
--hash=sha256:a8c769987d00fbc33054ff7e342633f475ea10dc43bc60fb9fb056159d48cb90 \
--hash=sha256:ba13d03b9e095216ceb4e4d554a308517f28ab0a6e4dcd07cfe94563e4c2c489 \
--hash=sha256:be8f457d7841b7ead2a3f6b65ba668abc172a1150a0f1f6c0958af3725dbb61a \
--hash=sha256:cc0880ec344fbdf736b05d8d0da01f0caaaa02409bd9a24b68d18d0127a79b0e \
--hash=sha256:e941e9a9d1e54b03eeaf9c3197c26a19cf76009fd5e41e16e5657c1c827bd6d3 \
--hash=sha256:ecf41706b803827b0de8717f32a434dad1e67be9f4b8caf403e12013179ea06a
ty==0.0.21 \
--hash=sha256:210e7568c9f886c4d01308d751949ee714ad7ad9d7d928d2ba90d329dd880367 \
--hash=sha256:53508e345b11569f78b21ba8e2b4e61df38a9754947fb3cd9f2ef574367338fb \
--hash=sha256:553e43571f4a35604c36cfd07d8b61a5eb7a714e3c67f8c4ff2cf674fefbaef9 \
--hash=sha256:56b01fd2519637a4ca88344f61c96225f540c98ff18bca321d4eaa7bb0f7aa2f \
--hash=sha256:56d3b198b64dd0a19b2b66e257deaed2ecea568e722ae5352f3c6fb62027f89d \
--hash=sha256:62f7f5b235c4f7876db305c36997aea07b7af29b1a068f373d0e2547e25f32ff \
--hash=sha256:666f6822e3b9200abfa7e95eb0ddd576460adb8d66b550c0ad2c70abc84a2048 \
--hash=sha256:7bdf2f572378de78e1f388d24691c89db51b7caf07cf90f2bfcc1d6b18b70a76 \
--hash=sha256:7e9613994610431ab8625025bd2880dbcb77c5c9fabdd21134cda12d840a529d \
--hash=sha256:a0854d008347ce4a5fb351af132f660a390ab2a1163444d075251d43e6f74b9b \
--hash=sha256:a4c2ba5d67d64df8fcdefd8b280ac1149d24a73dbda82fa953a0dff9d21400ed \
--hash=sha256:a709d576e5bea84b745d43058d8b9cd4f27f74a0b24acb4b0cbb7d3d41e0d050 \
--hash=sha256:bef3ab4c7b966bcc276a8ac6c11b63ba222d21355b48d471ea782c4104eee4e0 \
--hash=sha256:d23d2c34f7a77d974bb08f0860ef700addc8a683d81a0319f71c08f87506cfd0 \
--hash=sha256:e9de7e11c63c6afc40f3e9ba716374add171aee7fabc70b5146a510705c6d41b \
--hash=sha256:ee8399f7c453a425291e6688efe430cfae7ab0ac4ffd50eba9f872bf878b54f6 \
--hash=sha256:f72047996598ac20553fb7e21ba5741e3c82dee4e9eadf10d954551a5fe09391
# via -r src/backend/requirements-dev.in
types-psycopg2==2.9.21.20260223 \
--hash=sha256:78ed70de2e56bc6b5c26c8c1da8e9af54e49fdc3c94d1504609f3519e2b84f02 \