From 1b8a40f1ff1e20c94715e05e600fe6f056720883 Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Sat, 14 Feb 2026 22:56:56 +0100 Subject: [PATCH] feat(validators): add noqa suppression support to security and performance validators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add centralized _is_noqa_suppressed() to BaseValidator with normalization (accepts both SEC001 and SEC-001 formats for ruff compatibility) - Wire noqa support into all 21 security and 18 performance check functions - Add ruff external config for SEC/PERF/MOD/EXC codes in pyproject.toml - Convert all 280 Python noqa comments to dashless format (ruff-compatible) - Add site/ to IGNORE_PATTERNS (excludes mkdocs build output) - Suppress 152 false positive findings (test passwords, seed data, validator self-references, Apple Wallet SHA1, etc.) - Security: 79 errors → 0, 60 warnings → 0 - Performance: 80 warnings → 77 (3 test script suppressions) - Add proposal doc with noqa inventory and remaining findings recommendations Co-Authored-By: Claude Opus 4.6 --- app/modules/analytics/exceptions.py | 4 +- app/modules/billing/exceptions.py | 4 +- .../billing/services/billing_service.py | 2 +- .../tests/unit/test_billing_service.py | 10 +-- app/modules/cart/exceptions.py | 6 +- app/modules/catalog/exceptions.py | 8 +- app/modules/catalog/schemas/catalog.py | 2 +- app/modules/catalog/schemas/product.py | 2 +- .../catalog/tests/unit/test_product_model.py | 4 +- app/modules/checkout/exceptions.py | 18 ++--- app/modules/cms/exceptions.py | 18 ++--- .../cms/static/admin/js/content-page-edit.js | 2 +- .../cms/platform/sections/_features.html | 2 +- app/modules/core/exceptions.py | 6 +- .../static/storefront/js/storefront-layout.js | 5 +- app/modules/customers/exceptions.py | 6 +- .../customers/routes/api/storefront.py | 2 +- .../customers/services/customer_service.py | 2 +- .../tests/unit/test_admin_customer_service.py | 2 +- .../tests/unit/test_customer_model.py | 12 +-- .../tests/unit/test_customer_schema.py | 18 ++--- app/modules/dev_tools/exceptions.py | 6 +- .../services/code_quality_service.py | 2 +- app/modules/inventory/exceptions.py | 4 +- app/modules/loyalty/exceptions.py | 2 +- .../loyalty/services/apple_wallet_service.py | 8 +- .../loyalty/services/google_wallet_service.py | 12 +-- .../loyalty/services/wallet_service.py | 10 +-- app/modules/marketplace/exceptions.py | 32 ++++---- .../schemas/marketplace_import_job.py | 8 +- .../services/letzshop/store_sync_service.py | 4 +- .../services/platform_signup_service.py | 2 +- .../tests/unit/test_import_job_schema.py | 4 +- .../tests/unit/test_letzshop_service.py | 20 ++--- app/modules/messaging/exceptions.py | 4 +- .../messaging/services/email_service.py | 20 ++--- .../services/store_email_settings_service.py | 2 +- .../unit/test_store_email_settings_service.py | 6 +- app/modules/monitoring/exceptions.py | 12 +-- .../services/capacity_forecast_service.py | 4 +- app/modules/orders/exceptions.py | 10 +-- .../services/order_inventory_service.py | 2 +- app/modules/orders/services/order_service.py | 2 +- app/modules/payments/exceptions.py | 14 ++-- .../payments/services/gateway_service.py | 2 +- app/modules/tenancy/exceptions.py | 48 +++++------ app/modules/tenancy/models/merchant_domain.py | 2 +- app/modules/tenancy/models/store_domain.py | 2 +- .../tenancy/schemas/merchant_domain.py | 2 +- app/modules/tenancy/schemas/store_domain.py | 2 +- .../tests/unit/test_admin_platform_service.py | 10 +-- .../tests/unit/test_store_team_service.py | 12 +-- .../tenancy/tests/unit/test_user_model.py | 14 ++-- conftest.py | 2 +- ...oqa-suppressions-and-remaining-findings.md | 81 +++++++++++++++++++ mkdocs.yml | 1 + pyproject.toml | 3 + scripts/create_dummy_letzshop_order.py | 44 +++++----- scripts/seed/init_production.py | 4 +- scripts/seed/install.py | 4 +- scripts/seed/seed_demo.py | 38 ++++----- scripts/test_auth_complete.py | 4 +- scripts/test_store_management.py | 2 +- scripts/validate/base_validator.py | 30 ++++--- scripts/validate/validate_architecture.py | 8 +- scripts/validate/validate_audit.py | 4 +- scripts/validate/validate_security.py | 12 +-- static/shared/js/lib/quill.js | 4 +- .../shared/js/lib/tom-select.complete.min.js | 6 +- tests/fixtures/customer_fixtures.py | 2 +- tests/fixtures/testing_fixtures.py | 2 +- tests/unit/middleware/test_store_context.py | 8 +- tests/unit/models/schema/test_auth.py | 20 ++--- tests/unit/services/test_auth_service.py | 14 ++-- .../unit/services/test_marketplace_service.py | 2 +- 75 files changed, 404 insertions(+), 310 deletions(-) create mode 100644 docs/proposals/validator-noqa-suppressions-and-remaining-findings.md diff --git a/app/modules/analytics/exceptions.py b/app/modules/analytics/exceptions.py index f13fe5cf..a75fd50b 100644 --- a/app/modules/analytics/exceptions.py +++ b/app/modules/analytics/exceptions.py @@ -11,7 +11,7 @@ from app.exceptions.base import ( ) -class ReportGenerationException(BusinessLogicException): # noqa: MOD-025 +class ReportGenerationException(BusinessLogicException): # noqa: MOD025 """Raised when report generation fails.""" def __init__(self, report_type: str, reason: str): @@ -21,7 +21,7 @@ class ReportGenerationException(BusinessLogicException): # noqa: MOD-025 ) -class InvalidDateRangeException(ValidationException): # noqa: MOD-025 +class InvalidDateRangeException(ValidationException): # noqa: MOD025 """Raised when an invalid date range is provided.""" def __init__(self, start_date: str, end_date: str): diff --git a/app/modules/billing/exceptions.py b/app/modules/billing/exceptions.py index 75a3c15d..22eee06c 100644 --- a/app/modules/billing/exceptions.py +++ b/app/modules/billing/exceptions.py @@ -90,7 +90,7 @@ class SubscriptionNotCancelledException(BusinessLogicException): ) -class SubscriptionAlreadyCancelledException(BusinessLogicException): # noqa: MOD-025 +class SubscriptionAlreadyCancelledException(BusinessLogicException): # noqa: MOD025 """Raised when trying to cancel an already cancelled subscription.""" def __init__(self): @@ -170,7 +170,7 @@ class StripePriceNotConfiguredException(BusinessLogicException): self.tier_code = tier_code -class PaymentFailedException(BillingException): # noqa: MOD-025 +class PaymentFailedException(BillingException): # noqa: MOD025 """Raised when a payment fails.""" def __init__(self, message: str, stripe_error: str | None = None): diff --git a/app/modules/billing/services/billing_service.py b/app/modules/billing/services/billing_service.py index c39b6046..e83f283e 100644 --- a/app/modules/billing/services/billing_service.py +++ b/app/modules/billing/services/billing_service.py @@ -542,7 +542,7 @@ class BillingService: if stripe_service.is_configured and store_addon.stripe_subscription_item_id: try: stripe_service.cancel_subscription_item(store_addon.stripe_subscription_item_id) - except Exception as e: # noqa: EXC-003 + except Exception as e: # noqa: EXC003 logger.warning(f"Failed to cancel addon in Stripe: {e}") # Mark as cancelled diff --git a/app/modules/billing/tests/unit/test_billing_service.py b/app/modules/billing/tests/unit/test_billing_service.py index 8aa39ee1..fd902ec1 100644 --- a/app/modules/billing/tests/unit/test_billing_service.py +++ b/app/modules/billing/tests/unit/test_billing_service.py @@ -488,7 +488,7 @@ class TestBillingServiceCheckout: with pytest.raises(PaymentSystemNotConfiguredException): self.service.create_checkout_session( - db, 1, 1, "essential", False, "http://ok", "http://cancel" + db, 1, 1, "essential", False, "http://ok", "http://cancel" # noqa: SEC034 ) def test_checkout_nonexistent_tier_raises(self, db): @@ -500,7 +500,7 @@ class TestBillingServiceCheckout: with pytest.raises(TierNotFoundException): self.service.create_checkout_session( - db, 1, 1, "nonexistent", False, "http://ok", "http://cancel" + db, 1, 1, "nonexistent", False, "http://ok", "http://cancel" # noqa: SEC034 ) @@ -525,7 +525,7 @@ class TestBillingServicePortal: mock_stripe.is_configured = False with pytest.raises(PaymentSystemNotConfiguredException): - self.service.create_portal_session(db, 1, 1, "http://return") + self.service.create_portal_session(db, 1, 1, "http://return") # noqa: SEC034 def test_portal_no_subscription_raises(self, db): """Raises NoActiveSubscriptionException when no subscription found.""" @@ -535,7 +535,7 @@ class TestBillingServicePortal: mock_stripe.is_configured = True with pytest.raises(NoActiveSubscriptionException): - self.service.create_portal_session(db, 99999, 99999, "http://return") + self.service.create_portal_session(db, 99999, 99999, "http://return") # noqa: SEC034 def test_portal_no_customer_id_raises(self, db, bs_subscription): """Raises when subscription has no stripe_customer_id.""" @@ -549,7 +549,7 @@ class TestBillingServicePortal: db, bs_subscription.merchant_id, bs_subscription.platform_id, - "http://return", + "http://return", # noqa: SEC034 ) diff --git a/app/modules/cart/exceptions.py b/app/modules/cart/exceptions.py index e021f225..7e115ec8 100644 --- a/app/modules/cart/exceptions.py +++ b/app/modules/cart/exceptions.py @@ -37,7 +37,7 @@ class CartItemNotFoundException(ResourceNotFoundException): self.details.update({"product_id": product_id, "session_id": session_id}) -class EmptyCartException(ValidationException): # noqa: MOD-025 +class EmptyCartException(ValidationException): # noqa: MOD025 """Raised when trying to perform operations on an empty cart.""" def __init__(self, session_id: str): @@ -45,7 +45,7 @@ class EmptyCartException(ValidationException): # noqa: MOD-025 self.error_code = "CART_EMPTY" -class CartValidationException(ValidationException): # noqa: MOD-025 +class CartValidationException(ValidationException): # noqa: MOD025 """Raised when cart data validation fails.""" def __init__( @@ -113,7 +113,7 @@ class InvalidCartQuantityException(ValidationException): self.error_code = "INVALID_CART_QUANTITY" -class ProductNotAvailableForCartException(BusinessLogicException): # noqa: MOD-025 +class ProductNotAvailableForCartException(BusinessLogicException): # noqa: MOD025 """Raised when product is not available for adding to cart.""" def __init__(self, product_id: int, reason: str): diff --git a/app/modules/catalog/exceptions.py b/app/modules/catalog/exceptions.py index 63679be1..a66bc16a 100644 --- a/app/modules/catalog/exceptions.py +++ b/app/modules/catalog/exceptions.py @@ -63,7 +63,7 @@ class ProductAlreadyExistsException(ConflictException): ) -class ProductNotInCatalogException(ResourceNotFoundException): # noqa: MOD-025 +class ProductNotInCatalogException(ResourceNotFoundException): # noqa: MOD025 """Raised when trying to access a product that's not in store's catalog.""" def __init__(self, product_id: int, store_id: int): @@ -129,7 +129,7 @@ class ProductValidationException(ValidationException): self.error_code = "PRODUCT_VALIDATION_FAILED" -class CannotDeleteProductException(BusinessLogicException): # noqa: MOD-025 +class CannotDeleteProductException(BusinessLogicException): # noqa: MOD025 """Raised when a product cannot be deleted due to dependencies.""" def __init__(self, product_id: int, reason: str, details: dict | None = None): @@ -140,7 +140,7 @@ class CannotDeleteProductException(BusinessLogicException): # noqa: MOD-025 ) -class CannotDeleteProductWithInventoryException(BusinessLogicException): # noqa: MOD-025 +class CannotDeleteProductWithInventoryException(BusinessLogicException): # noqa: MOD025 """Raised when trying to delete a product that has inventory.""" def __init__(self, product_id: int, inventory_count: int): @@ -154,7 +154,7 @@ class CannotDeleteProductWithInventoryException(BusinessLogicException): # noqa ) -class CannotDeleteProductWithOrdersException(BusinessLogicException): # noqa: MOD-025 +class CannotDeleteProductWithOrdersException(BusinessLogicException): # noqa: MOD025 """Raised when trying to delete a product that has been ordered.""" def __init__(self, product_id: int, order_count: int): diff --git a/app/modules/catalog/schemas/catalog.py b/app/modules/catalog/schemas/catalog.py index 44c7ff03..511da1ef 100644 --- a/app/modules/catalog/schemas/catalog.py +++ b/app/modules/catalog/schemas/catalog.py @@ -10,7 +10,7 @@ from datetime import datetime from pydantic import BaseModel, ConfigDict -from app.modules.inventory.schemas import InventoryLocationResponse # noqa: IMPORT-002 +from app.modules.inventory.schemas import InventoryLocationResponse # noqa: IMPORT002 from app.modules.marketplace.schemas import MarketplaceProductResponse # IMPORT-002 diff --git a/app/modules/catalog/schemas/product.py b/app/modules/catalog/schemas/product.py index 6a6f2dc7..d7488722 100644 --- a/app/modules/catalog/schemas/product.py +++ b/app/modules/catalog/schemas/product.py @@ -10,7 +10,7 @@ from datetime import datetime from pydantic import BaseModel, ConfigDict, Field -from app.modules.inventory.schemas import InventoryLocationResponse # noqa: IMPORT-002 +from app.modules.inventory.schemas import InventoryLocationResponse # noqa: IMPORT002 from app.modules.marketplace.schemas import MarketplaceProductResponse # IMPORT-002 diff --git a/app/modules/catalog/tests/unit/test_product_model.py b/app/modules/catalog/tests/unit/test_product_model.py index c19c8bc5..17265c4c 100644 --- a/app/modules/catalog/tests/unit/test_product_model.py +++ b/app/modules/catalog/tests/unit/test_product_model.py @@ -306,7 +306,7 @@ class TestProductInventoryProperties: def test_physical_product_with_inventory(self, db, test_store): """Test physical product calculates inventory from entries.""" - from app.modules.inventory.models import Inventory # noqa: IMPORT-002 + from app.modules.inventory.models import Inventory # noqa: IMPORT002 product = Product( store_id=test_store.id, @@ -364,7 +364,7 @@ class TestProductInventoryProperties: def test_digital_product_ignores_inventory_entries(self, db, test_store): """Test digital product returns unlimited even with inventory entries.""" - from app.modules.inventory.models import Inventory # noqa: IMPORT-002 + from app.modules.inventory.models import Inventory # noqa: IMPORT002 product = Product( store_id=test_store.id, diff --git a/app/modules/checkout/exceptions.py b/app/modules/checkout/exceptions.py index a372e7d0..f7b3438b 100644 --- a/app/modules/checkout/exceptions.py +++ b/app/modules/checkout/exceptions.py @@ -12,7 +12,7 @@ from app.exceptions.base import ( ) -class CheckoutValidationException(ValidationException): # noqa: MOD-025 +class CheckoutValidationException(ValidationException): # noqa: MOD025 """Raised when checkout data validation fails.""" def __init__( @@ -29,7 +29,7 @@ class CheckoutValidationException(ValidationException): # noqa: MOD-025 self.error_code = "CHECKOUT_VALIDATION_FAILED" -class CheckoutSessionNotFoundException(ResourceNotFoundException): # noqa: MOD-025 +class CheckoutSessionNotFoundException(ResourceNotFoundException): # noqa: MOD025 """Raised when checkout session is not found.""" def __init__(self, session_id: str): @@ -41,7 +41,7 @@ class CheckoutSessionNotFoundException(ResourceNotFoundException): # noqa: MOD- ) -class CheckoutSessionExpiredException(BusinessLogicException): # noqa: MOD-025 +class CheckoutSessionExpiredException(BusinessLogicException): # noqa: MOD025 """Raised when checkout session has expired.""" def __init__(self, session_id: str): @@ -52,7 +52,7 @@ class CheckoutSessionExpiredException(BusinessLogicException): # noqa: MOD-025 ) -class EmptyCheckoutException(ValidationException): # noqa: MOD-025 +class EmptyCheckoutException(ValidationException): # noqa: MOD025 """Raised when trying to checkout with empty cart.""" def __init__(self): @@ -63,7 +63,7 @@ class EmptyCheckoutException(ValidationException): # noqa: MOD-025 self.error_code = "EMPTY_CHECKOUT" -class PaymentRequiredException(BusinessLogicException): # noqa: MOD-025 +class PaymentRequiredException(BusinessLogicException): # noqa: MOD025 """Raised when payment is required but not provided.""" def __init__(self, order_total: float): @@ -74,7 +74,7 @@ class PaymentRequiredException(BusinessLogicException): # noqa: MOD-025 ) -class PaymentFailedException(BusinessLogicException): # noqa: MOD-025 +class PaymentFailedException(BusinessLogicException): # noqa: MOD025 """Raised when payment processing fails.""" def __init__(self, reason: str, details: dict | None = None): @@ -85,7 +85,7 @@ class PaymentFailedException(BusinessLogicException): # noqa: MOD-025 ) -class InvalidShippingAddressException(ValidationException): # noqa: MOD-025 +class InvalidShippingAddressException(ValidationException): # noqa: MOD025 """Raised when shipping address is invalid or missing.""" def __init__(self, message: str = "Invalid shipping address", details: dict | None = None): @@ -97,7 +97,7 @@ class InvalidShippingAddressException(ValidationException): # noqa: MOD-025 self.error_code = "INVALID_SHIPPING_ADDRESS" -class ShippingMethodNotAvailableException(BusinessLogicException): # noqa: MOD-025 +class ShippingMethodNotAvailableException(BusinessLogicException): # noqa: MOD025 """Raised when selected shipping method is not available.""" def __init__(self, shipping_method: str, reason: str | None = None): @@ -111,7 +111,7 @@ class ShippingMethodNotAvailableException(BusinessLogicException): # noqa: MOD- ) -class CheckoutInventoryException(BusinessLogicException): # noqa: MOD-025 +class CheckoutInventoryException(BusinessLogicException): # noqa: MOD025 """Raised when inventory check fails during checkout.""" def __init__(self, product_id: int, available: int, requested: int): diff --git a/app/modules/cms/exceptions.py b/app/modules/cms/exceptions.py index a8bc706b..07d70ac8 100644 --- a/app/modules/cms/exceptions.py +++ b/app/modules/cms/exceptions.py @@ -69,7 +69,7 @@ class ContentPageNotFoundException(ResourceNotFoundException): ) -class ContentPageAlreadyExistsException(ConflictException): # noqa: MOD-025 +class ContentPageAlreadyExistsException(ConflictException): # noqa: MOD025 """Raised when a content page with the same slug already exists.""" def __init__(self, slug: str, store_id: int | None = None): @@ -84,7 +84,7 @@ class ContentPageAlreadyExistsException(ConflictException): # noqa: MOD-025 ) -class ContentPageSlugReservedException(ValidationException): # noqa: MOD-025 +class ContentPageSlugReservedException(ValidationException): # noqa: MOD025 """Raised when trying to use a reserved slug.""" def __init__(self, slug: str): @@ -96,7 +96,7 @@ class ContentPageSlugReservedException(ValidationException): # noqa: MOD-025 self.error_code = "CONTENT_PAGE_SLUG_RESERVED" -class ContentPageNotPublishedException(BusinessLogicException): # noqa: MOD-025 +class ContentPageNotPublishedException(BusinessLogicException): # noqa: MOD025 """Raised when trying to access an unpublished content page.""" def __init__(self, slug: str): @@ -118,7 +118,7 @@ class UnauthorizedContentPageAccessException(AuthorizationException): ) -class StoreNotAssociatedException(AuthorizationException): # noqa: MOD-025 +class StoreNotAssociatedException(AuthorizationException): # noqa: MOD025 """Raised when a user is not associated with a store.""" def __init__(self): @@ -143,7 +143,7 @@ class NoPlatformSubscriptionException(BusinessLogicException): ) -class ContentPageValidationException(ValidationException): # noqa: MOD-025 +class ContentPageValidationException(ValidationException): # noqa: MOD025 """Raised when content page data validation fails.""" def __init__(self, field: str, message: str, value: str | None = None): @@ -175,7 +175,7 @@ class MediaNotFoundException(ResourceNotFoundException): ) -class MediaUploadException(BusinessLogicException): # noqa: MOD-025 +class MediaUploadException(BusinessLogicException): # noqa: MOD025 """Raised when media upload fails.""" def __init__(self, message: str = "Media upload failed", details: dict[str, Any] | None = None): @@ -241,7 +241,7 @@ class MediaOptimizationException(BusinessLogicException): ) -class MediaDeleteException(BusinessLogicException): # noqa: MOD-025 +class MediaDeleteException(BusinessLogicException): # noqa: MOD025 """Raised when media deletion fails.""" def __init__(self, message: str, details: dict[str, Any] | None = None): @@ -269,7 +269,7 @@ class StoreThemeNotFoundException(ResourceNotFoundException): ) -class InvalidThemeDataException(ValidationException): # noqa: MOD-025 +class InvalidThemeDataException(ValidationException): # noqa: MOD025 """Raised when theme data is invalid.""" def __init__( @@ -321,7 +321,7 @@ class ThemeValidationException(ValidationException): self.error_code = "THEME_VALIDATION_FAILED" -class ThemePresetAlreadyAppliedException(BusinessLogicException): # noqa: MOD-025 +class ThemePresetAlreadyAppliedException(BusinessLogicException): # noqa: MOD025 """Raised when trying to apply the same preset that's already active.""" def __init__(self, preset_name: str, store_code: str): diff --git a/app/modules/cms/static/admin/js/content-page-edit.js b/app/modules/cms/static/admin/js/content-page-edit.js index 30b28314..a1cd5658 100644 --- a/app/modules/cms/static/admin/js/content-page-edit.js +++ b/app/modules/cms/static/admin/js/content-page-edit.js @@ -234,7 +234,7 @@ function contentPageEditor(pageId) { const quill = quillContainer.__quill; if (this.form.content && quill.root.innerHTML !== this.form.content) { - quill.root.innerHTML = this.form.content; + quill.root.innerHTML = this.form.content; // # noqa: SEC-015 contentPageEditLog.debug('Synced Quill content after page load'); } }, diff --git a/app/modules/cms/templates/cms/platform/sections/_features.html b/app/modules/cms/templates/cms/platform/sections/_features.html index 9df04dc7..f30b3916 100644 --- a/app/modules/cms/templates/cms/platform/sections/_features.html +++ b/app/modules/cms/templates/cms/platform/sections/_features.html @@ -40,7 +40,7 @@
{# Support for icon names - rendered via Alpine $icon helper or direct SVG #} {% if feature.icon.startswith(' {% else %} {% endif %} diff --git a/app/modules/core/exceptions.py b/app/modules/core/exceptions.py index 20e98acc..7d5f3654 100644 --- a/app/modules/core/exceptions.py +++ b/app/modules/core/exceptions.py @@ -10,12 +10,12 @@ Exceptions for core platform functionality including: from app.exceptions import OrionException -class CoreException(OrionException): # noqa: MOD-025 +class CoreException(OrionException): # noqa: MOD025 """Base exception for core module.""" -class MenuConfigurationError(CoreException): # noqa: MOD-025 +class MenuConfigurationError(CoreException): # noqa: MOD025 """Error in menu configuration.""" @@ -25,5 +25,5 @@ class SettingsError(CoreException): -class DashboardError(CoreException): # noqa: MOD-025 +class DashboardError(CoreException): # noqa: MOD025 """Error in dashboard operations.""" diff --git a/app/modules/core/static/storefront/js/storefront-layout.js b/app/modules/core/static/storefront/js/storefront-layout.js index f54e3750..507542e7 100644 --- a/app/modules/core/static/storefront/js/storefront-layout.js +++ b/app/modules/core/static/storefront/js/storefront-layout.js @@ -197,8 +197,7 @@ function shopLayoutData() { info: 'bg-blue-500' }; - // noqa: SEC-015 - message is application-controlled - toast.innerHTML = ` + toast.innerHTML /* # noqa: SEC-015 */ = `
${message}