fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.) - Added ignore rules for patterns intentional in this codebase: E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from), SIM108/SIM105/SIM117 (readability preferences) - Added per-file ignores for tests and scripts - Excluded broken scripts/rename_terminology.py (has curly quotes) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,6 @@ This ensures module translations are loaded before use.
|
||||
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
|
||||
|
||||
@@ -10,8 +10,9 @@ Example:
|
||||
python scripts/check_letzshop_shipment.py abc123 R532332163
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
ENDPOINT = "https://letzshop.lu/graphql"
|
||||
@@ -132,52 +133,48 @@ def search_shipment(api_key: str, search_term: str):
|
||||
order_number = order.get("number", "")
|
||||
|
||||
# Check if this matches our search term
|
||||
if (search_term in shipment_id or
|
||||
search_term in shipment_number or
|
||||
search_term in order_number or
|
||||
search_term == shipment_id or
|
||||
search_term == order_number):
|
||||
if (search_term in (shipment_id, order_number) or search_term in shipment_id or search_term in shipment_number or search_term in order_number):
|
||||
|
||||
print(f"\n{'=' * 60}")
|
||||
print(f"FOUND SHIPMENT!")
|
||||
print("FOUND SHIPMENT!")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
print(f"\n--- Shipment Info ---")
|
||||
print("\n--- Shipment Info ---")
|
||||
print(f" ID: {shipment.get('id')}")
|
||||
print(f" Number: {shipment.get('number')}")
|
||||
print(f" State: {shipment.get('state')}")
|
||||
|
||||
print(f"\n--- Order Info ---")
|
||||
print("\n--- Order Info ---")
|
||||
print(f" Order ID: {order.get('id')}")
|
||||
print(f" Order Number: {order.get('number')}")
|
||||
print(f" Email: {order.get('email')}")
|
||||
print(f" Total: {order.get('total')}")
|
||||
print(f" Completed At: {order.get('completedAt')}")
|
||||
|
||||
ship_addr = order.get('shipAddress', {})
|
||||
ship_addr = order.get("shipAddress", {})
|
||||
if ship_addr:
|
||||
print(f"\n--- Shipping Address ---")
|
||||
print("\n--- Shipping Address ---")
|
||||
print(f" Name: {ship_addr.get('firstName')} {ship_addr.get('lastName')}")
|
||||
print(f" Street: {ship_addr.get('streetName')} {ship_addr.get('streetNumber')}")
|
||||
print(f" City: {ship_addr.get('zipCode')} {ship_addr.get('city')}")
|
||||
country = ship_addr.get('country', {})
|
||||
country = ship_addr.get("country", {})
|
||||
print(f" Country: {country.get('iso')}")
|
||||
|
||||
print(f"\n--- Inventory Units ---")
|
||||
units = shipment.get('inventoryUnits', [])
|
||||
print("\n--- Inventory Units ---")
|
||||
units = shipment.get("inventoryUnits", [])
|
||||
for i, unit in enumerate(units, 1):
|
||||
print(f" Unit {i}:")
|
||||
print(f" ID: {unit.get('id')}")
|
||||
print(f" State: {unit.get('state')}")
|
||||
variant = unit.get('variant', {})
|
||||
variant = unit.get("variant", {})
|
||||
print(f" SKU: {variant.get('sku')}")
|
||||
trade_id = variant.get('tradeId', {})
|
||||
trade_id = variant.get("tradeId", {})
|
||||
print(f" GTIN: {trade_id.get('number')}")
|
||||
product = variant.get('product', {})
|
||||
name = product.get('name', {})
|
||||
product = variant.get("product", {})
|
||||
name = product.get("name", {})
|
||||
print(f" Product: {name.get('en')}")
|
||||
|
||||
print(f"\n--- Raw Response ---")
|
||||
print("\n--- Raw Response ---")
|
||||
print(json.dumps(shipment, indent=2, default=str))
|
||||
return shipment
|
||||
|
||||
|
||||
@@ -9,8 +9,9 @@ Example:
|
||||
python scripts/check_letzshop_tracking.py abc123 nvDv5RQEmCwbjo
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
ENDPOINT = "https://letzshop.lu/graphql"
|
||||
@@ -80,13 +81,13 @@ def get_tracking_info(api_key: str, target_shipment_id: str):
|
||||
if response.status_code != 200:
|
||||
print(f"Error: HTTP {response.status_code}")
|
||||
print(response.text)
|
||||
return
|
||||
return None
|
||||
|
||||
data = response.json()
|
||||
|
||||
if "errors" in data:
|
||||
print(f"GraphQL errors: {json.dumps(data['errors'], indent=2)}")
|
||||
return
|
||||
return None
|
||||
|
||||
result = data.get("data", {}).get("shipments", {})
|
||||
nodes = result.get("nodes", [])
|
||||
@@ -100,29 +101,29 @@ def get_tracking_info(api_key: str, target_shipment_id: str):
|
||||
print("FOUND SHIPMENT!")
|
||||
print(f"{'=' * 60}")
|
||||
|
||||
print(f"\n--- Shipment Info ---")
|
||||
print("\n--- Shipment Info ---")
|
||||
print(f" ID: {shipment.get('id')}")
|
||||
print(f" Number: {shipment.get('number')}")
|
||||
print(f" State: {shipment.get('state')}")
|
||||
|
||||
print(f"\n--- Tracking Info ---")
|
||||
tracking = shipment.get('tracking')
|
||||
print("\n--- Tracking Info ---")
|
||||
tracking = shipment.get("tracking")
|
||||
if tracking:
|
||||
print(f" Tracking Number: {tracking.get('number')}")
|
||||
print(f" Tracking URL: {tracking.get('url')}")
|
||||
carrier = tracking.get('carrier', {})
|
||||
carrier = tracking.get("carrier", {})
|
||||
if carrier:
|
||||
print(f" Carrier Name: {carrier.get('name')}")
|
||||
print(f" Carrier Code: {carrier.get('code')}")
|
||||
else:
|
||||
print(" No tracking object returned")
|
||||
|
||||
print(f"\n--- Order Info ---")
|
||||
order = shipment.get('order', {})
|
||||
print("\n--- Order Info ---")
|
||||
order = shipment.get("order", {})
|
||||
print(f" Order Number: {order.get('number')}")
|
||||
print(f" Email: {order.get('email')}")
|
||||
|
||||
print(f"\n--- Raw Response ---")
|
||||
print("\n--- Raw Response ---")
|
||||
print(json.dumps(shipment, indent=2, default=str))
|
||||
return shipment
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Usage:
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
import requests
|
||||
|
||||
ENDPOINT = "https://letzshop.lu/graphql"
|
||||
@@ -177,7 +177,7 @@ def main():
|
||||
node = result.get("data", {}).get("node", {})
|
||||
tracking = node.get("tracking")
|
||||
if tracking:
|
||||
print(f" Tracking via node query:")
|
||||
print(" Tracking via node query:")
|
||||
print(f" Code: {tracking.get('code')}")
|
||||
print(f" Provider: {tracking.get('provider')}")
|
||||
else:
|
||||
|
||||
@@ -15,16 +15,15 @@ import argparse
|
||||
import random
|
||||
import string
|
||||
import sys
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
# Add project root to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.utils.money import cents_to_euros, euros_to_cents
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.tenancy.models import Store
|
||||
|
||||
|
||||
@@ -41,7 +40,7 @@ def generate_shipment_number():
|
||||
def generate_hash_id():
|
||||
"""Generate a realistic hash ID like nvDv5RQEmCwbjo."""
|
||||
chars = string.ascii_letters + string.digits
|
||||
return ''.join(random.choice(chars) for _ in range(14))
|
||||
return "".join(random.choice(chars) for _ in range(14))
|
||||
|
||||
|
||||
def create_dummy_order(
|
||||
@@ -97,7 +96,7 @@ def create_dummy_order(
|
||||
order_number = generate_order_number()
|
||||
shipment_number = generate_shipment_number()
|
||||
hash_id = generate_hash_id()
|
||||
order_date = datetime.now(timezone.utc) - timedelta(days=random.randint(0, 7))
|
||||
order_date = datetime.now(UTC) - timedelta(days=random.randint(0, 7))
|
||||
|
||||
# Customer data
|
||||
first_names = ["Jean", "Marie", "Pierre", "Sophie", "Michel", "Anne", "Thomas", "Claire"]
|
||||
@@ -180,7 +179,7 @@ def create_dummy_order(
|
||||
db.flush()
|
||||
|
||||
# Create order items with prices in cents
|
||||
for i, product in enumerate(products[:items_count]):
|
||||
for _i, product in enumerate(products[:items_count]):
|
||||
quantity = random.randint(1, 3)
|
||||
unit_price_cents = product.price_cents or 0
|
||||
product_name = product.get_title("en") or f"Product {product.id}"
|
||||
|
||||
@@ -33,7 +33,7 @@ cursor = conn.cursor()
|
||||
# Get products without inventory
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT p.id, p.store_id, p.product_id
|
||||
SELECT p.id, p.store_id, p.product_id
|
||||
FROM products p
|
||||
LEFT JOIN inventory i ON p.id = i.product_id
|
||||
WHERE i.id IS NULL
|
||||
@@ -49,7 +49,7 @@ if not products_without_inventory:
|
||||
print(f"📦 Creating inventory for {len(products_without_inventory)} products...")
|
||||
|
||||
# Create inventory entries
|
||||
for product_id, store_id, sku in products_without_inventory:
|
||||
for product_id, store_id, _sku in products_without_inventory:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO inventory (
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"""Debug script to investigate order shipping information."""
|
||||
|
||||
import sys
|
||||
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
@@ -82,15 +83,15 @@ def investigate_order(order_number: str):
|
||||
print(f" Keys: {list(ext.keys())}")
|
||||
|
||||
# Check for tracking info in external data
|
||||
if 'tracking' in ext:
|
||||
if "tracking" in ext:
|
||||
print(f" tracking: {ext['tracking']}")
|
||||
if 'trackingNumber' in ext:
|
||||
if "trackingNumber" in ext:
|
||||
print(f" trackingNumber: {ext['trackingNumber']}")
|
||||
if 'carrier' in ext:
|
||||
if "carrier" in ext:
|
||||
print(f" carrier: {ext['carrier']}")
|
||||
if 'state' in ext:
|
||||
if "state" in ext:
|
||||
print(f" state: {ext['state']}")
|
||||
if 'shipmentState' in ext:
|
||||
if "shipmentState" in ext:
|
||||
print(f" shipmentState: {ext['shipmentState']}")
|
||||
|
||||
# Print full external data (truncated if too long)
|
||||
|
||||
@@ -9,9 +9,10 @@ Usage:
|
||||
python scripts/letzshop_introspect.py YOUR_API_KEY
|
||||
"""
|
||||
|
||||
import sys
|
||||
import requests
|
||||
import json
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
ENDPOINT = "https://letzshop.lu/graphql"
|
||||
|
||||
@@ -227,10 +228,9 @@ def format_type(type_info: dict) -> str:
|
||||
|
||||
if kind == "NON_NULL":
|
||||
return f"{format_type(of_type)}!"
|
||||
elif kind == "LIST":
|
||||
if kind == "LIST":
|
||||
return f"[{format_type(of_type)}]"
|
||||
else:
|
||||
return name or kind
|
||||
return name or kind
|
||||
|
||||
|
||||
def print_fields(type_data: dict, highlight_terms: list[str] = None):
|
||||
@@ -490,7 +490,7 @@ def test_shipment_query(api_key: str, state: str = "unconfirmed"):
|
||||
result = run_query(api_key, query)
|
||||
|
||||
if "errors" in result:
|
||||
print(f"\n❌ QUERY FAILED!")
|
||||
print("\n❌ QUERY FAILED!")
|
||||
print(f"Errors: {json.dumps(result['errors'], indent=2)}")
|
||||
return False
|
||||
|
||||
@@ -503,7 +503,7 @@ def test_shipment_query(api_key: str, state: str = "unconfirmed"):
|
||||
order = shipment.get("order", {})
|
||||
units = shipment.get("inventoryUnits", [])
|
||||
|
||||
print(f"\nExample shipment:")
|
||||
print("\nExample shipment:")
|
||||
print(f" Shipment #: {shipment.get('number')}")
|
||||
print(f" Order #: {order.get('number')}")
|
||||
print(f" Customer: {order.get('email')}")
|
||||
|
||||
@@ -6,8 +6,8 @@ Extracts messages, creates translation keys, and updates both JS and locale file
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
MODULES_DIR = PROJECT_ROOT / "app" / "modules"
|
||||
@@ -42,11 +42,11 @@ def message_to_key(message: str) -> str:
|
||||
"""Convert a message string to a translation key."""
|
||||
# Remove special characters and convert to snake_case
|
||||
key = message.lower()
|
||||
key = re.sub(r'[^\w\s]', '', key)
|
||||
key = re.sub(r'\s+', '_', key)
|
||||
key = re.sub(r"[^\w\s]", "", key)
|
||||
key = re.sub(r"\s+", "_", key)
|
||||
# Truncate if too long
|
||||
if len(key) > 40:
|
||||
key = key[:40].rstrip('_')
|
||||
key = key[:40].rstrip("_")
|
||||
return key
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ def process_module(module_name: str, js_files: list[Path]) -> dict[str, str]:
|
||||
# Extract all unique messages
|
||||
for js_file in js_files:
|
||||
messages = extract_messages_from_file(js_file)
|
||||
for message, msg_type in messages:
|
||||
for message, _msg_type in messages:
|
||||
if message not in all_messages:
|
||||
all_messages[message] = message_to_key(message)
|
||||
|
||||
@@ -158,7 +158,7 @@ def main():
|
||||
print(f" {key}: {msg[:50]}{'...' if len(msg) > 50 else ''}")
|
||||
|
||||
# Update locale files for all languages
|
||||
print(f"\n Updating locale files...")
|
||||
print("\n Updating locale files...")
|
||||
for lang in LANGUAGES:
|
||||
locale_data = load_locale_file(module_path, lang)
|
||||
|
||||
@@ -177,7 +177,7 @@ def main():
|
||||
print(f" Updated: {lang}.json")
|
||||
|
||||
# Update JS files
|
||||
print(f"\n Updating JS files...")
|
||||
print("\n Updating JS files...")
|
||||
for js_file in js_files:
|
||||
if update_js_file(js_file, module_name, message_keys):
|
||||
rel_path = js_file.relative_to(PROJECT_ROOT)
|
||||
|
||||
@@ -20,8 +20,8 @@ import json
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from pathlib import Path
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -136,7 +136,7 @@ class ModuleDependencyAnalyzer:
|
||||
in_type_checking = False
|
||||
type_checking_indent = 0
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
for _i, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#") or not stripped:
|
||||
continue
|
||||
@@ -167,7 +167,7 @@ class ModuleDependencyAnalyzer:
|
||||
|
||||
# Look for import statements
|
||||
import_match = re.match(
|
||||
r'^\s*(?:from\s+(app\.modules\.(\w+))|import\s+(app\.modules\.(\w+)))',
|
||||
r"^\s*(?:from\s+(app\.modules\.(\w+))|import\s+(app\.modules\.(\w+)))",
|
||||
line
|
||||
)
|
||||
|
||||
@@ -291,13 +291,13 @@ def format_mermaid(analyzer: ModuleDependencyAnalyzer) -> str:
|
||||
lines = []
|
||||
lines.append("```mermaid")
|
||||
lines.append("flowchart TD")
|
||||
lines.append(" subgraph core[\"Core Modules\"]")
|
||||
lines.append(' subgraph core["Core Modules"]')
|
||||
for module in sorted(analyzer.CORE_MODULES):
|
||||
if module in analyzer.modules:
|
||||
lines.append(f" {module}[{module}]")
|
||||
lines.append(" end")
|
||||
lines.append("")
|
||||
lines.append(" subgraph optional[\"Optional Modules\"]")
|
||||
lines.append(' subgraph optional["Optional Modules"]')
|
||||
for module in sorted(analyzer.OPTIONAL_MODULES):
|
||||
if module in analyzer.modules:
|
||||
lines.append(f" {module}[{module}]")
|
||||
@@ -306,7 +306,7 @@ def format_mermaid(analyzer: ModuleDependencyAnalyzer) -> str:
|
||||
|
||||
# Add edges
|
||||
for source, deps in analyzer.dependencies.items():
|
||||
for target, dep in deps.items():
|
||||
for target, _dep in deps.items():
|
||||
# Style violations differently
|
||||
source_is_core = source in analyzer.CORE_MODULES
|
||||
target_is_optional = target in analyzer.OPTIONAL_MODULES
|
||||
@@ -323,43 +323,43 @@ def format_dot(analyzer: ModuleDependencyAnalyzer) -> str:
|
||||
"""Format output as GraphViz DOT."""
|
||||
lines = []
|
||||
lines.append("digraph ModuleDependencies {")
|
||||
lines.append(' rankdir=LR;')
|
||||
lines.append(' node [shape=box];')
|
||||
lines.append('')
|
||||
lines.append(" rankdir=LR;")
|
||||
lines.append(" node [shape=box];")
|
||||
lines.append("")
|
||||
|
||||
# Core modules cluster
|
||||
lines.append(' subgraph cluster_core {')
|
||||
lines.append(" subgraph cluster_core {")
|
||||
lines.append(' label="Core Modules";')
|
||||
lines.append(' style=filled;')
|
||||
lines.append(' color=lightblue;')
|
||||
lines.append(" style=filled;")
|
||||
lines.append(" color=lightblue;")
|
||||
for module in sorted(analyzer.CORE_MODULES):
|
||||
if module in analyzer.modules:
|
||||
lines.append(f' {module};')
|
||||
lines.append(' }')
|
||||
lines.append('')
|
||||
lines.append(f" {module};")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
|
||||
# Optional modules cluster
|
||||
lines.append(' subgraph cluster_optional {')
|
||||
lines.append(" subgraph cluster_optional {")
|
||||
lines.append(' label="Optional Modules";')
|
||||
lines.append(' style=filled;')
|
||||
lines.append(' color=lightyellow;')
|
||||
lines.append(" style=filled;")
|
||||
lines.append(" color=lightyellow;")
|
||||
for module in sorted(analyzer.OPTIONAL_MODULES):
|
||||
if module in analyzer.modules:
|
||||
lines.append(f' {module};')
|
||||
lines.append(' }')
|
||||
lines.append('')
|
||||
lines.append(f" {module};")
|
||||
lines.append(" }")
|
||||
lines.append("")
|
||||
|
||||
# Edges
|
||||
for source, deps in analyzer.dependencies.items():
|
||||
for target, dep in deps.items():
|
||||
for target, _dep in deps.items():
|
||||
source_is_core = source in analyzer.CORE_MODULES
|
||||
target_is_optional = target in analyzer.OPTIONAL_MODULES
|
||||
if source_is_core and target_is_optional:
|
||||
lines.append(f' {source} -> {target} [color=red, style=dashed, label="violation"];')
|
||||
else:
|
||||
lines.append(f' {source} -> {target};')
|
||||
lines.append(f" {source} -> {target};")
|
||||
|
||||
lines.append('}')
|
||||
lines.append("}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ from pathlib import Path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
import contextlib
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -49,10 +51,8 @@ for _mod in [
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.cms.models import ContentPage
|
||||
@@ -505,7 +505,7 @@ def create_default_pages(db: Session) -> None:
|
||||
# Check if page already exists (platform default with this slug)
|
||||
existing = db.execute(
|
||||
select(ContentPage).where(
|
||||
ContentPage.store_id == None, ContentPage.slug == page_data["slug"]
|
||||
ContentPage.store_id is None, ContentPage.slug == page_data["slug"]
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ Usage:
|
||||
python scripts/create_platform_pages.py
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -36,10 +37,8 @@ for _mod in [
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from sqlalchemy import select
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ Run this script to create default logging configuration settings.
|
||||
"""
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
import contextlib
|
||||
|
||||
for _mod in [
|
||||
"app.modules.billing.models",
|
||||
"app.modules.inventory.models",
|
||||
@@ -18,10 +20,8 @@ for _mod in [
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.tenancy.models import AdminSetting
|
||||
|
||||
@@ -24,6 +24,8 @@ from pathlib import Path
|
||||
project_root = Path(__file__).parent.parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
import contextlib
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
@@ -34,13 +36,12 @@ from app.core.config import (
|
||||
)
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.environment import is_production
|
||||
from app.modules.billing.models.subscription import SubscriptionTier
|
||||
from app.modules.tenancy.models import AdminSetting, Platform, User
|
||||
from app.modules.tenancy.services.permission_discovery_service import (
|
||||
permission_discovery_service,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.tenancy.models import AdminSetting, Platform
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.billing.models.subscription import SubscriptionTier
|
||||
|
||||
# Register all models with SQLAlchemy so string-based relationships resolve
|
||||
for _mod in [
|
||||
@@ -55,10 +56,8 @@ for _mod in [
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# =============================================================================
|
||||
# HELPER FUNCTIONS
|
||||
@@ -510,7 +509,7 @@ def initialize_production(db: Session, auth_manager: AuthManager):
|
||||
|
||||
# Step 2: Create admin user
|
||||
print_step(2, "Creating platform admin...")
|
||||
admin = create_admin_user(db, auth_manager)
|
||||
create_admin_user(db, auth_manager)
|
||||
|
||||
# Step 3: Create default platforms
|
||||
print_step(3, "Creating default platforms...")
|
||||
@@ -518,7 +517,7 @@ def initialize_production(db: Session, auth_manager: AuthManager):
|
||||
|
||||
# Step 4: Set up default role templates
|
||||
print_step(4, "Setting up role templates...")
|
||||
role_templates = create_default_role_templates(db)
|
||||
create_default_role_templates(db)
|
||||
|
||||
# Step 5: Create admin settings
|
||||
print_step(5, "Creating admin settings...")
|
||||
|
||||
@@ -18,7 +18,6 @@ Usage:
|
||||
This script is idempotent - safe to run multiple times.
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -107,9 +106,8 @@ def check_env_file() -> tuple[bool, dict]:
|
||||
print_info("Copy .env.example to .env and configure it:")
|
||||
print_info(" cp .env.example .env")
|
||||
return False, {}
|
||||
else:
|
||||
print_warning("Neither .env nor .env.example found")
|
||||
return False, {}
|
||||
print_warning("Neither .env nor .env.example found")
|
||||
return False, {}
|
||||
|
||||
print_success(".env file found")
|
||||
|
||||
@@ -451,11 +449,10 @@ def run_migrations() -> bool:
|
||||
if result.returncode == 0:
|
||||
print_success("Migrations completed successfully")
|
||||
return True
|
||||
else:
|
||||
print_error("Migration failed")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:500])
|
||||
return False
|
||||
print_error("Migration failed")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:500])
|
||||
return False
|
||||
except Exception as e:
|
||||
print_error(f"Failed to run migrations: {e}")
|
||||
return False
|
||||
@@ -474,11 +471,10 @@ def run_init_script(script_name: str, description: str) -> bool:
|
||||
if result.returncode == 0:
|
||||
print_success(description)
|
||||
return True
|
||||
else:
|
||||
print_error(f"Failed: {description}")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:300])
|
||||
return False
|
||||
print_error(f"Failed: {description}")
|
||||
if result.stderr:
|
||||
print_info(result.stderr[:300])
|
||||
return False
|
||||
except Exception as e:
|
||||
print_error(f"Error running {script_name}: {e}")
|
||||
return False
|
||||
@@ -578,7 +574,7 @@ def main():
|
||||
|
||||
print(f"\n {Colors.BOLD}Admin Login:{Colors.ENDC}")
|
||||
admin_email = env_vars.get("ADMIN_EMAIL", "admin@wizamart.com")
|
||||
print(f" URL: /admin/login")
|
||||
print(" URL: /admin/login")
|
||||
print(f" Email: {admin_email}")
|
||||
print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}")
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@ sys.path.insert(0, str(project_root))
|
||||
# =============================================================================
|
||||
# MODE DETECTION (from environment variable set by Makefile)
|
||||
# =============================================================================
|
||||
import contextlib
|
||||
import os
|
||||
|
||||
from sqlalchemy import delete, select
|
||||
@@ -50,25 +51,33 @@ from sqlalchemy.orm import Session
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal
|
||||
from app.core.environment import get_environment, is_production
|
||||
from middleware.auth import AuthManager
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.cms.models import ContentPage, StoreTheme
|
||||
from app.modules.customers.models.customer import Customer, CustomerAddress
|
||||
from app.modules.marketplace.models import (
|
||||
MarketplaceImportJob,
|
||||
MarketplaceProduct,
|
||||
MarketplaceProductTranslation,
|
||||
)
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
|
||||
# =============================================================================
|
||||
# MODEL IMPORTS
|
||||
# =============================================================================
|
||||
# ALL models must be imported before any ORM query so SQLAlchemy can resolve
|
||||
# cross-module string relationships (e.g. Store→StoreEmailTemplate,
|
||||
# Platform→SubscriptionTier, Product→Inventory).
|
||||
|
||||
# Core modules
|
||||
from app.modules.tenancy.models import Merchant, PlatformAlert, User, Role, Store, StoreUser, StoreDomain
|
||||
from app.modules.cms.models import ContentPage, StoreTheme
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.customers.models.customer import Customer, CustomerAddress
|
||||
from app.modules.orders.models import Order, OrderItem
|
||||
from app.modules.marketplace.models import (
|
||||
MarketplaceImportJob,
|
||||
MarketplaceProduct,
|
||||
MarketplaceProductTranslation,
|
||||
from app.modules.tenancy.models import (
|
||||
Merchant,
|
||||
PlatformAlert,
|
||||
Role,
|
||||
Store,
|
||||
StoreDomain,
|
||||
StoreUser,
|
||||
User,
|
||||
)
|
||||
from middleware.auth import AuthManager
|
||||
|
||||
# Optional modules — import to register models with SQLAlchemy
|
||||
for _mod in [
|
||||
@@ -78,10 +87,8 @@ for _mod in [
|
||||
"app.modules.messaging.models",
|
||||
"app.modules.loyalty.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
SEED_MODE = os.getenv("SEED_MODE", "normal") # normal, minimal, reset
|
||||
FORCE_RESET = os.getenv("FORCE_RESET", "false").lower() in ("true", "1", "yes")
|
||||
@@ -562,7 +569,7 @@ def reset_all_data(db: Session):
|
||||
for table in tables_to_clear:
|
||||
if table == ContentPage:
|
||||
# Only delete store content pages, keep platform defaults
|
||||
db.execute(delete(ContentPage).where(ContentPage.store_id != None))
|
||||
db.execute(delete(ContentPage).where(ContentPage.store_id is not None))
|
||||
else:
|
||||
db.execute(delete(table))
|
||||
|
||||
@@ -1104,8 +1111,8 @@ def print_summary(db: Session):
|
||||
team_member_count = db.query(StoreUser).filter(StoreUser.user_type == "member").count()
|
||||
customer_count = db.query(Customer).count()
|
||||
product_count = db.query(Product).count()
|
||||
platform_pages = db.query(ContentPage).filter(ContentPage.store_id == None).count()
|
||||
store_pages = db.query(ContentPage).filter(ContentPage.store_id != None).count()
|
||||
platform_pages = db.query(ContentPage).filter(ContentPage.store_id is None).count()
|
||||
store_pages = db.query(ContentPage).filter(ContentPage.store_id is not None).count()
|
||||
|
||||
print("\n📊 Database Status:")
|
||||
print(f" Merchants: {merchant_count}")
|
||||
|
||||
@@ -5,6 +5,7 @@ Seed default email templates.
|
||||
Run: python scripts/seed_email_templates.py
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
@@ -25,15 +26,12 @@ for _mod in [
|
||||
"app.modules.marketplace.models",
|
||||
"app.modules.cms.models",
|
||||
]:
|
||||
try:
|
||||
with contextlib.suppress(ImportError):
|
||||
__import__(_mod)
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.modules.messaging.models import EmailCategory, EmailTemplate
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# EMAIL TEMPLATES
|
||||
# =============================================================================
|
||||
|
||||
@@ -23,7 +23,7 @@ def count_files(directory: str, pattern: str) -> int:
|
||||
return 0
|
||||
|
||||
count = 0
|
||||
for root, dirs, files in os.walk(directory):
|
||||
for _root, dirs, files in os.walk(directory):
|
||||
# Skip __pycache__ and other cache directories
|
||||
dirs[:] = [
|
||||
d
|
||||
|
||||
@@ -21,7 +21,6 @@ from sqlalchemy import text
|
||||
from app.core.config import settings
|
||||
from app.core.database import SessionLocal
|
||||
|
||||
|
||||
DEV_BASE = "http://localhost:9999"
|
||||
SEPARATOR = "─" * 72
|
||||
|
||||
|
||||
@@ -100,10 +100,9 @@ def backup_migrations():
|
||||
if total_backed_up > 0:
|
||||
print(f"Backed up {total_backed_up} migrations to {backup_dir.name}/")
|
||||
return backup_dir
|
||||
else:
|
||||
print("No migration files found to backup")
|
||||
backup_dir.rmdir()
|
||||
return None
|
||||
print("No migration files found to backup")
|
||||
backup_dir.rmdir()
|
||||
return None
|
||||
|
||||
|
||||
def create_fresh_migration():
|
||||
@@ -180,7 +179,7 @@ def main():
|
||||
|
||||
# Confirm with user
|
||||
response = input("This will backup and replace all migrations. Continue? [y/N] ")
|
||||
if response.lower() != 'y':
|
||||
if response.lower() != "y":
|
||||
print("Aborted")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -461,10 +461,9 @@ def test_query(api_key: str, query_name: str, query: str, page_size: int = 5, sh
|
||||
else:
|
||||
print("OK - no node returned")
|
||||
return True
|
||||
else:
|
||||
shipments = data.get("data", {}).get("shipments", {}).get("nodes", [])
|
||||
print(f"OK - {len(shipments)} shipments")
|
||||
return True
|
||||
shipments = data.get("data", {}).get("shipments", {}).get("nodes", [])
|
||||
print(f"OK - {len(shipments)} shipments")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"ERROR - {e}")
|
||||
@@ -570,7 +569,7 @@ def main():
|
||||
failing = [name for name, success in results.items() if success is False]
|
||||
|
||||
if failing:
|
||||
print(f"\n⚠️ Problem detected!")
|
||||
print("\n⚠️ Problem detected!")
|
||||
print(f" Working queries: {', '.join(working) if working else 'none'}")
|
||||
print(f" Failing queries: {', '.join(failing)}")
|
||||
|
||||
@@ -664,11 +663,11 @@ def main():
|
||||
print(f"Total items: {total_items}")
|
||||
print(f"Unique EANs: {len(eans)}")
|
||||
|
||||
print(f"\nOrders by locale:")
|
||||
print("\nOrders by locale:")
|
||||
for locale, count in sorted(locales.items(), key=lambda x: -x[1]):
|
||||
print(f" {locale}: {count}")
|
||||
|
||||
print(f"\nOrders by country:")
|
||||
print("\nOrders by country:")
|
||||
for country, count in sorted(countries.items(), key=lambda x: -x[1]):
|
||||
print(f" {country}: {count}")
|
||||
|
||||
|
||||
@@ -90,7 +90,9 @@ def test_logging_endpoints():
|
||||
print("\n[4] Testing log settings...")
|
||||
try:
|
||||
from app.core.database import SessionLocal
|
||||
from app.modules.core.services.admin_settings_service import admin_settings_service
|
||||
from app.modules.core.services.admin_settings_service import (
|
||||
admin_settings_service,
|
||||
)
|
||||
|
||||
db = SessionLocal()
|
||||
try:
|
||||
|
||||
@@ -240,7 +240,7 @@ def main():
|
||||
store_id_1 = test_create_store_with_both_emails()
|
||||
|
||||
# Test 2: Create with single email
|
||||
store_id_2 = test_create_store_single_email()
|
||||
test_create_store_single_email()
|
||||
|
||||
if store_id_1:
|
||||
# Test 3: Update contact email
|
||||
|
||||
@@ -4,7 +4,7 @@ Base Validator Class
|
||||
Shared functionality for all validators.
|
||||
"""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from abc import ABC
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
@@ -100,7 +100,7 @@ class BaseValidator(ABC):
|
||||
Subclasses should implement validate_all() instead.
|
||||
"""
|
||||
result = self.validate_all()
|
||||
return not result.has_errors() if hasattr(result, 'has_errors') else True
|
||||
return not result.has_errors() if hasattr(result, "has_errors") else True
|
||||
|
||||
def validate_all(self, target_path: Path | None = None) -> ValidationResult:
|
||||
"""Run all validations. Override in subclasses."""
|
||||
@@ -178,10 +178,7 @@ class BaseValidator(ABC):
|
||||
def _should_ignore_file(self, file_path: Path) -> bool:
|
||||
"""Check if a file should be ignored based on patterns."""
|
||||
path_str = str(file_path)
|
||||
for pattern in self.IGNORE_PATTERNS:
|
||||
if pattern in path_str:
|
||||
return True
|
||||
return False
|
||||
return any(pattern in path_str for pattern in self.IGNORE_PATTERNS)
|
||||
|
||||
def _add_violation(
|
||||
self,
|
||||
@@ -224,7 +221,6 @@ class BaseValidator(ABC):
|
||||
|
||||
def _validate_file_content(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""Validate file content. Override in subclasses."""
|
||||
pass
|
||||
|
||||
def output_results(self, json_output: bool = False, errors_only: bool = False) -> None:
|
||||
"""Output validation results."""
|
||||
|
||||
@@ -129,10 +129,10 @@ def run_audit_validator(verbose: bool = False) -> tuple[int, dict]:
|
||||
1 if has_errors else 0,
|
||||
{
|
||||
"name": "Audit",
|
||||
"files_checked": len(validator.files_checked) if hasattr(validator, 'files_checked') else 0,
|
||||
"files_checked": len(validator.files_checked) if hasattr(validator, "files_checked") else 0,
|
||||
"errors": len(validator.errors),
|
||||
"warnings": len(validator.warnings),
|
||||
"info": len(validator.info) if hasattr(validator, 'info') else 0,
|
||||
"info": len(validator.info) if hasattr(validator, "info") else 0,
|
||||
}
|
||||
)
|
||||
except ImportError as e:
|
||||
|
||||
@@ -484,7 +484,7 @@ class ArchitectureValidator:
|
||||
stripped = line.strip()
|
||||
|
||||
# Skip comments
|
||||
if stripped.startswith("//") or stripped.startswith("/*"):
|
||||
if stripped.startswith(("//", "/*")):
|
||||
continue
|
||||
|
||||
# Skip lines with inline noqa comment
|
||||
@@ -583,7 +583,7 @@ class ArchitectureValidator:
|
||||
if re.search(r"\bfetch\s*\(", line):
|
||||
# Skip if it's a comment
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("//") or stripped.startswith("/*"):
|
||||
if stripped.startswith(("//", "/*")):
|
||||
continue
|
||||
|
||||
# Check if it's calling an API endpoint (contains /api/)
|
||||
@@ -757,10 +757,6 @@ class ArchitectureValidator:
|
||||
has_loading_state = (
|
||||
"loading:" in component_region or "loading :" in component_region
|
||||
)
|
||||
has_loading_assignment = (
|
||||
"this.loading = " in component_region
|
||||
or "loading = true" in component_region
|
||||
)
|
||||
|
||||
if has_api_calls and not has_loading_state:
|
||||
line_num = content[:func_start].count("\n") + 1
|
||||
@@ -1119,7 +1115,6 @@ class ArchitectureValidator:
|
||||
return
|
||||
|
||||
# Valid admin template blocks
|
||||
valid_blocks = {"title", "extra_head", "alpine_data", "content", "extra_scripts"}
|
||||
|
||||
# Common invalid block names that developers might mistakenly use
|
||||
invalid_blocks = {
|
||||
@@ -1200,7 +1195,6 @@ class ArchitectureValidator:
|
||||
|
||||
# Track multi-line copyCode template literals with double-quoted outer attribute
|
||||
in_copycode_template = False
|
||||
copycode_start_line = 0
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
if "noqa: tpl-012" in line.lower():
|
||||
@@ -1208,9 +1202,8 @@ class ArchitectureValidator:
|
||||
|
||||
# Check for start of copyCode with double-quoted attribute and template literal
|
||||
# Pattern: @click="copyCode(` where the backtick doesn't close on same line
|
||||
if '@click="copyCode(`' in line and '`)' not in line:
|
||||
if '@click="copyCode(`' in line and "`)" not in line:
|
||||
in_copycode_template = True
|
||||
copycode_start_line = i
|
||||
continue
|
||||
|
||||
# Check for end of copyCode template (backtick followed by )" or )')
|
||||
@@ -1272,7 +1265,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message=f"Old pagination API with '{param_name}' parameter",
|
||||
context=line.strip()[:80],
|
||||
suggestion="Use: {{ pagination(show_condition=\"!loading && pagination.total > 0\") }}",
|
||||
suggestion='Use: {{ pagination(show_condition="!loading && pagination.total > 0") }}',
|
||||
)
|
||||
break # Only report once per line
|
||||
|
||||
@@ -1433,7 +1426,7 @@ class ArchitectureValidator:
|
||||
if pattern in line:
|
||||
# Skip if it's in a comment
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("{#") or stripped.startswith("<!--"):
|
||||
if stripped.startswith(("{#", "<!--")):
|
||||
continue
|
||||
|
||||
self._add_violation(
|
||||
@@ -1730,9 +1723,7 @@ class ArchitectureValidator:
|
||||
# Skip if it's in a comment
|
||||
stripped = line.strip()
|
||||
if (
|
||||
stripped.startswith("{#")
|
||||
or stripped.startswith("<!--")
|
||||
or stripped.startswith("//")
|
||||
stripped.startswith(("{#", "<!--", "//"))
|
||||
):
|
||||
continue
|
||||
|
||||
@@ -1753,6 +1744,7 @@ class ArchitectureValidator:
|
||||
print("📡 Validating API endpoints...")
|
||||
|
||||
api_files = list(target_path.glob("app/api/v1/**/*.py"))
|
||||
api_files += list(target_path.glob("app/modules/*/routes/api/**/*.py"))
|
||||
self.result.files_checked += len(api_files)
|
||||
|
||||
for file_path in api_files:
|
||||
@@ -1795,7 +1787,7 @@ class ArchitectureValidator:
|
||||
if re.search(route_pattern, line):
|
||||
# Look ahead for function body
|
||||
func_start = i
|
||||
indent = len(line) - len(line.lstrip())
|
||||
len(line) - len(line.lstrip())
|
||||
|
||||
# Find function body
|
||||
for j in range(func_start, min(func_start + 20, len(lines))):
|
||||
@@ -1953,7 +1945,9 @@ class ArchitectureValidator:
|
||||
|
||||
# Skip auth endpoint files entirely - they are intentionally public
|
||||
file_path_str = str(file_path)
|
||||
if file_path_str.endswith("/auth.py") or file_path_str.endswith("\\auth.py"):
|
||||
if file_path_str.endswith(("/auth.py", "\\auth.py")):
|
||||
return
|
||||
if "_auth.py" in file_path.name:
|
||||
return
|
||||
|
||||
# This is a warning-level check
|
||||
@@ -1966,6 +1960,7 @@ class ArchitectureValidator:
|
||||
# - Depends(get_user_permissions) - permission fetching
|
||||
auth_patterns = [
|
||||
"Depends(get_current_",
|
||||
"Depends(get_merchant_for_current_user",
|
||||
"Depends(require_store_",
|
||||
"Depends(require_any_store_",
|
||||
"Depends(require_all_store",
|
||||
@@ -2646,9 +2641,12 @@ class ArchitectureValidator:
|
||||
|
||||
# NAM-001: API files use PLURAL names
|
||||
api_files = list(target_path.glob("app/api/v1/**/*.py"))
|
||||
api_files += list(target_path.glob("app/modules/*/routes/api/**/*.py"))
|
||||
for file_path in api_files:
|
||||
if file_path.name in ["__init__.py", "auth.py", "health.py"]:
|
||||
continue
|
||||
if "_auth.py" in file_path.name:
|
||||
continue
|
||||
self._check_api_file_naming(file_path)
|
||||
|
||||
# NAM-002: Service files use SINGULAR + 'service' suffix
|
||||
@@ -2791,7 +2789,7 @@ class ArchitectureValidator:
|
||||
# NAM-004: Check for 'shop_id' (should be store_id)
|
||||
# Skip shop-specific files where shop_id might be legitimate
|
||||
# Use word boundary to avoid matching 'letzshop_id' etc.
|
||||
shop_id_pattern = re.compile(r'\bshop_id\b')
|
||||
shop_id_pattern = re.compile(r"\bshop_id\b")
|
||||
if "/shop/" not in str(file_path):
|
||||
for i, line in enumerate(lines, 1):
|
||||
if shop_id_pattern.search(line) and "# noqa" not in line.lower():
|
||||
@@ -3182,7 +3180,7 @@ class ArchitectureValidator:
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Skip comments
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("//") or stripped.startswith("*"):
|
||||
if stripped.startswith(("//", "*")):
|
||||
continue
|
||||
|
||||
# Check for apiClient calls with /api/v1 prefix
|
||||
@@ -3689,7 +3687,7 @@ class ArchitectureValidator:
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Skip comments
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("//") or stripped.startswith("/*"):
|
||||
if stripped.startswith(("//", "/*")):
|
||||
continue
|
||||
|
||||
# LANG-005: Check for English language names instead of native
|
||||
@@ -4040,7 +4038,7 @@ class ArchitectureValidator:
|
||||
file_path=definition_file,
|
||||
line_number=1,
|
||||
message=f"Module '{module_name}' is self-contained but missing '{req_dir}/' directory",
|
||||
context=f"is_self_contained=True",
|
||||
context="is_self_contained=True",
|
||||
suggestion=f"Create '{req_dir}/' directory with __init__.py",
|
||||
)
|
||||
elif req_dir != "routes":
|
||||
@@ -4054,7 +4052,7 @@ class ArchitectureValidator:
|
||||
file_path=definition_file,
|
||||
line_number=1,
|
||||
message=f"Module '{module_name}' missing '{req_dir}/__init__.py'",
|
||||
context=f"is_self_contained=True",
|
||||
context="is_self_contained=True",
|
||||
suggestion=f"Create '{req_dir}/__init__.py' with exports",
|
||||
)
|
||||
|
||||
@@ -4068,7 +4066,7 @@ class ArchitectureValidator:
|
||||
file_path=definition_file,
|
||||
line_number=1,
|
||||
message=f"Module '{module_name}' is self-contained but missing 'routes/api/' directory",
|
||||
context=f"is_self_contained=True",
|
||||
context="is_self_contained=True",
|
||||
suggestion="Create 'routes/api/' directory for API endpoints",
|
||||
)
|
||||
|
||||
@@ -4107,7 +4105,7 @@ class ArchitectureValidator:
|
||||
severity=Severity.WARNING,
|
||||
file_path=route_file,
|
||||
line_number=i,
|
||||
message=f"Route imports from legacy 'app.services' instead of module services",
|
||||
message="Route imports from legacy 'app.services' instead of module services",
|
||||
context=line.strip()[:80],
|
||||
suggestion=f"Import from 'app.modules.{module_name}.services' or '..services'",
|
||||
)
|
||||
@@ -4228,7 +4226,6 @@ class ArchitectureValidator:
|
||||
py_files = list(dir_path.glob("*.py"))
|
||||
|
||||
# Track if we found any file with actual code (not just re-exports)
|
||||
has_actual_code = False
|
||||
|
||||
for py_file in py_files:
|
||||
if py_file.name == "__init__.py":
|
||||
@@ -4250,7 +4247,7 @@ class ArchitectureValidator:
|
||||
)
|
||||
|
||||
if has_definitions and not has_reexport:
|
||||
has_actual_code = True
|
||||
pass
|
||||
elif has_reexport and not has_definitions:
|
||||
# File is a re-export only
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -4262,9 +4259,9 @@ class ArchitectureValidator:
|
||||
severity=Severity.WARNING,
|
||||
file_path=py_file,
|
||||
line_number=i,
|
||||
message=f"File re-exports from legacy location instead of containing actual code",
|
||||
message="File re-exports from legacy location instead of containing actual code",
|
||||
context=line.strip()[:80],
|
||||
suggestion=f"Move actual code to this file and update legacy to re-export from here",
|
||||
suggestion="Move actual code to this file and update legacy to re-export from here",
|
||||
)
|
||||
break
|
||||
|
||||
@@ -4301,7 +4298,7 @@ class ArchitectureValidator:
|
||||
severity=Severity.WARNING,
|
||||
file_path=file_path,
|
||||
line_number=1,
|
||||
message=f"Route file missing 'router' variable for auto-discovery",
|
||||
message="Route file missing 'router' variable for auto-discovery",
|
||||
context=f"routes/{route_type}/{route_file}",
|
||||
suggestion="Add: router = APIRouter() and use @router.get/post decorators",
|
||||
)
|
||||
@@ -4349,7 +4346,7 @@ class ArchitectureValidator:
|
||||
severity=Severity.ERROR,
|
||||
file_path=definition_file,
|
||||
line_number=1,
|
||||
message=f"Module definition specifies 'exceptions_path' but no exceptions module exists",
|
||||
message="Module definition specifies 'exceptions_path' but no exceptions module exists",
|
||||
context=f"exceptions_path=app.modules.{module_name}.exceptions",
|
||||
suggestion="Create 'exceptions.py' or 'exceptions/__init__.py'",
|
||||
)
|
||||
@@ -4608,7 +4605,7 @@ class ArchitectureValidator:
|
||||
|
||||
# Look for import statements
|
||||
import_match = re.match(
|
||||
r'^\s*(?:from\s+(app\.modules\.(\w+))|import\s+(app\.modules\.(\w+)))',
|
||||
r"^\s*(?:from\s+(app\.modules\.(\w+))|import\s+(app\.modules\.(\w+)))",
|
||||
line
|
||||
)
|
||||
|
||||
@@ -4654,7 +4651,7 @@ class ArchitectureValidator:
|
||||
line_number=i,
|
||||
message=f"Core module '{module_name}' imports from optional module '{imported_module}'",
|
||||
context=stripped[:80],
|
||||
suggestion=f"Use provider pattern (MetricsProvider, WidgetProvider) or contracts protocol instead. See docs/architecture/cross-module-import-rules.md",
|
||||
suggestion="Use provider pattern (MetricsProvider, WidgetProvider) or contracts protocol instead. See docs/architecture/cross-module-import-rules.md",
|
||||
)
|
||||
|
||||
# IMPORT-002: Optional module importing from unrelated optional module
|
||||
@@ -5176,25 +5173,22 @@ def main():
|
||||
# Determine validation mode
|
||||
if args.file:
|
||||
# Validate single file
|
||||
result = validator.validate_file(args.file)
|
||||
validator.validate_file(args.file)
|
||||
elif args.folder:
|
||||
# Validate directory
|
||||
if not args.folder.is_dir():
|
||||
print(f"❌ Not a directory: {args.folder}")
|
||||
sys.exit(1)
|
||||
result = validator.validate_all(args.folder)
|
||||
validator.validate_all(args.folder)
|
||||
elif args.object:
|
||||
# Validate all files related to an entity
|
||||
result = validator.validate_object(args.object)
|
||||
validator.validate_object(args.object)
|
||||
else:
|
||||
# Default: validate current directory
|
||||
result = validator.validate_all(Path.cwd())
|
||||
validator.validate_all(Path.cwd())
|
||||
|
||||
# Output results
|
||||
if args.json:
|
||||
exit_code = validator.print_json()
|
||||
else:
|
||||
exit_code = validator.print_report()
|
||||
exit_code = validator.print_json() if args.json else validator.print_report()
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Add parent directory to path for imports
|
||||
sys.path.insert(0, str(Path(__file__).parent))
|
||||
|
||||
@@ -529,7 +528,7 @@ def main() -> int:
|
||||
default="text",
|
||||
help="Output format",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
parser.parse_args()
|
||||
|
||||
validator = AuditValidator()
|
||||
validator.load_rules()
|
||||
|
||||
@@ -191,12 +191,12 @@ class PerformanceValidator(BaseValidator):
|
||||
stripped = line.strip()
|
||||
|
||||
# Track for loops over query results
|
||||
if re.search(r'for\s+\w+\s+in\s+.*\.(all|query)', line):
|
||||
if re.search(r"for\s+\w+\s+in\s+.*\.(all|query)", line):
|
||||
in_for_loop = True
|
||||
for_line_num = i
|
||||
elif in_for_loop and stripped and not stripped.startswith("#"):
|
||||
# Check for relationship access in loop
|
||||
if re.search(r'\.\w+\.\w+', line) and "(" not in line:
|
||||
if re.search(r"\.\w+\.\w+", line) and "(" not in line:
|
||||
# Could be accessing a relationship
|
||||
if any(rel in line for rel in [".customer.", ".store.", ".order.", ".product.", ".user."]):
|
||||
self._add_violation(
|
||||
@@ -218,7 +218,7 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_query_limiting(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-003: Check for unbounded query results"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'\.all\(\)', line):
|
||||
if re.search(r"\.all\(\)", line):
|
||||
# Check if there's a limit or filter before
|
||||
context_start = max(0, i - 5)
|
||||
context_lines = lines[context_start:i]
|
||||
@@ -247,7 +247,7 @@ class PerformanceValidator(BaseValidator):
|
||||
stripped = line.strip()
|
||||
|
||||
# Track for loops
|
||||
if re.search(r'for\s+\w+\s+in\s+', line):
|
||||
if re.search(r"for\s+\w+\s+in\s+", line):
|
||||
in_for_loop = True
|
||||
for_indent = len(line) - len(line.lstrip())
|
||||
elif in_for_loop:
|
||||
@@ -270,9 +270,9 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_existence_checks(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-008: Check for inefficient existence checks"""
|
||||
patterns = [
|
||||
(r'\.count\(\)\s*>\s*0', "count() > 0"),
|
||||
(r'\.count\(\)\s*>=\s*1', "count() >= 1"),
|
||||
(r'\.count\(\)\s*!=\s*0', "count() != 0"),
|
||||
(r"\.count\(\)\s*>\s*0", "count() > 0"),
|
||||
(r"\.count\(\)\s*>=\s*1", "count() >= 1"),
|
||||
(r"\.count\(\)\s*!=\s*0", "count() != 0"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -299,7 +299,7 @@ class PerformanceValidator(BaseValidator):
|
||||
stripped = line.strip()
|
||||
|
||||
# Track for loops
|
||||
match = re.search(r'for\s+(\w+)\s+in\s+', line)
|
||||
match = re.search(r"for\s+(\w+)\s+in\s+", line)
|
||||
if match:
|
||||
in_for_loop = True
|
||||
for_indent = len(line) - len(line.lstrip())
|
||||
@@ -336,16 +336,16 @@ class PerformanceValidator(BaseValidator):
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Track router decorators
|
||||
if re.search(r'@router\.(get|post)', line):
|
||||
if re.search(r"@router\.(get|post)", line):
|
||||
in_endpoint = True
|
||||
endpoint_line = i
|
||||
has_pagination = False
|
||||
elif in_endpoint:
|
||||
# Check for pagination parameters
|
||||
if re.search(r'(skip|offset|page|limit)', line):
|
||||
if re.search(r"(skip|offset|page|limit)", line):
|
||||
has_pagination = True
|
||||
# Check for function end
|
||||
if re.search(r'^def\s+\w+', line.lstrip()) and i > endpoint_line + 1:
|
||||
if re.search(r"^def\s+\w+", line.lstrip()) and i > endpoint_line + 1:
|
||||
in_endpoint = False
|
||||
# Check for .all() without pagination
|
||||
if ".all()" in line and not has_pagination:
|
||||
@@ -405,8 +405,8 @@ class PerformanceValidator(BaseValidator):
|
||||
return
|
||||
|
||||
patterns = [
|
||||
r'requests\.(get|post|put|delete|patch)\s*\([^)]+\)',
|
||||
r'httpx\.(get|post|put|delete|patch)\s*\([^)]+\)',
|
||||
r"requests\.(get|post|put|delete|patch)\s*\([^)]+\)",
|
||||
r"httpx\.(get|post|put|delete|patch)\s*\([^)]+\)",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -451,7 +451,7 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_file_streaming(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-047: Check for loading entire files into memory"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'await\s+\w+\.read\(\)', line) and "chunk" not in line:
|
||||
if re.search(r"await\s+\w+\.read\(\)", line) and "chunk" not in line:
|
||||
self._add_violation(
|
||||
rule_id="PERF-047",
|
||||
rule_name="Stream large file uploads",
|
||||
@@ -483,7 +483,7 @@ class PerformanceValidator(BaseValidator):
|
||||
"""PERF-049: Check for file handles without context managers"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Check for file open without 'with'
|
||||
if re.search(r'^\s*\w+\s*=\s*open\s*\(', line):
|
||||
if re.search(r"^\s*\w+\s*=\s*open\s*\(", line):
|
||||
if "# noqa" not in line:
|
||||
self._add_violation(
|
||||
rule_id="PERF-049",
|
||||
@@ -504,7 +504,7 @@ class PerformanceValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
stripped = line.strip()
|
||||
|
||||
if re.search(r'for\s+\w+\s+in\s+', line):
|
||||
if re.search(r"for\s+\w+\s+in\s+", line):
|
||||
in_for_loop = True
|
||||
for_indent = len(line) - len(line.lstrip())
|
||||
elif in_for_loop:
|
||||
@@ -548,7 +548,7 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_polling_intervals(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-062: Check for too-frequent polling"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
match = re.search(r'setInterval\s*\([^,]+,\s*(\d+)\s*\)', line)
|
||||
match = re.search(r"setInterval\s*\([^,]+,\s*(\d+)\s*\)", line)
|
||||
if match:
|
||||
interval = int(match.group(1))
|
||||
if interval < 10000: # Less than 10 seconds
|
||||
@@ -568,7 +568,7 @@ class PerformanceValidator(BaseValidator):
|
||||
"""PERF-064: Check for layout thrashing patterns"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Check for read then write patterns
|
||||
if re.search(r'(offsetHeight|offsetWidth|clientHeight|clientWidth)', line):
|
||||
if re.search(r"(offsetHeight|offsetWidth|clientHeight|clientWidth)", line):
|
||||
if i < len(lines):
|
||||
next_line = lines[i] if i < len(lines) else ""
|
||||
if "style" in next_line:
|
||||
@@ -586,7 +586,7 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_image_lazy_loading(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-058: Check for images without lazy loading"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'<img\s+[^>]*src=', line):
|
||||
if re.search(r"<img\s+[^>]*src=", line):
|
||||
if 'loading="lazy"' not in line and "x-intersect" not in line:
|
||||
if "logo" not in line.lower() and "icon" not in line.lower():
|
||||
self._add_violation(
|
||||
@@ -603,7 +603,7 @@ class PerformanceValidator(BaseValidator):
|
||||
def _check_script_loading(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""PERF-067: Check for script tags without defer/async"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'<script\s+[^>]*src=', line):
|
||||
if re.search(r"<script\s+[^>]*src=", line):
|
||||
if "defer" not in line and "async" not in line:
|
||||
if "alpine" not in line.lower() and "htmx" not in line.lower():
|
||||
self._add_violation(
|
||||
|
||||
@@ -191,7 +191,7 @@ class SecurityValidator(BaseValidator):
|
||||
|
||||
# Check for eval usage
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'\beval\s*\(', line) and "//" not in line.split("eval")[0]:
|
||||
if re.search(r"\beval\s*\(", line) and "//" not in line.split("eval")[0]:
|
||||
self._add_violation(
|
||||
rule_id="SEC-013",
|
||||
rule_name="No code execution",
|
||||
@@ -205,7 +205,7 @@ class SecurityValidator(BaseValidator):
|
||||
|
||||
# Check for innerHTML with user input
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'\.innerHTML\s*=', line) and "//" not in line.split("innerHTML")[0]:
|
||||
if re.search(r"\.innerHTML\s*=", line) and "//" not in line.split("innerHTML")[0]:
|
||||
self._add_violation(
|
||||
rule_id="SEC-015",
|
||||
rule_name="XSS prevention",
|
||||
@@ -221,7 +221,7 @@ class SecurityValidator(BaseValidator):
|
||||
"""Validate HTML template file for security issues"""
|
||||
# SEC-015: XSS via |safe filter
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'\|\s*safe', line) and 'sanitized' not in line.lower():
|
||||
if re.search(r"\|\s*safe", line) and "sanitized" not in line.lower():
|
||||
self._add_violation(
|
||||
rule_id="SEC-015",
|
||||
rule_name="XSS prevention in templates",
|
||||
@@ -260,7 +260,7 @@ class SecurityValidator(BaseValidator):
|
||||
for i, line in enumerate(lines, 1):
|
||||
# Skip comments
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("#") or stripped.startswith("//"):
|
||||
if stripped.startswith(("#", "//")):
|
||||
continue
|
||||
|
||||
for pattern, secret_type in secret_patterns:
|
||||
@@ -320,8 +320,8 @@ class SecurityValidator(BaseValidator):
|
||||
"""SEC-011: Check for SQL injection vulnerabilities"""
|
||||
patterns = [
|
||||
r'execute\s*\(\s*f["\']',
|
||||
r'execute\s*\([^)]*\s*\+\s*',
|
||||
r'execute\s*\([^)]*%[^)]*%',
|
||||
r"execute\s*\([^)]*\s*\+\s*",
|
||||
r"execute\s*\([^)]*%[^)]*%",
|
||||
r'text\s*\(\s*f["\']',
|
||||
r'\.raw\s*\(\s*f["\']',
|
||||
]
|
||||
@@ -345,9 +345,9 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_command_injection(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-012: Check for command injection vulnerabilities"""
|
||||
patterns = [
|
||||
(r'subprocess.*shell\s*=\s*True', "shell=True in subprocess"),
|
||||
(r'os\.system\s*\(', "os.system()"),
|
||||
(r'os\.popen\s*\(', "os.popen()"),
|
||||
(r"subprocess.*shell\s*=\s*True", "shell=True in subprocess"),
|
||||
(r"os\.system\s*\(", "os.system()"),
|
||||
(r"os\.popen\s*\(", "os.popen()"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -369,10 +369,10 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_code_execution(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-013: Check for code execution vulnerabilities"""
|
||||
patterns = [
|
||||
(r'eval\s*\([^)]*request', "eval with request data"),
|
||||
(r'eval\s*\([^)]*input', "eval with user input"),
|
||||
(r'exec\s*\([^)]*request', "exec with request data"),
|
||||
(r'__import__\s*\([^)]*request', "__import__ with request data"),
|
||||
(r"eval\s*\([^)]*request", "eval with request data"),
|
||||
(r"eval\s*\([^)]*input", "eval with user input"),
|
||||
(r"exec\s*\([^)]*request", "exec with request data"),
|
||||
(r"__import__\s*\([^)]*request", "__import__ with request data"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -395,9 +395,9 @@ class SecurityValidator(BaseValidator):
|
||||
has_secure_filename = "secure_filename" in content or "basename" in content
|
||||
|
||||
patterns = [
|
||||
r'open\s*\([^)]*request',
|
||||
r'open\s*\([^)]*\+',
|
||||
r'Path\s*\([^)]*request',
|
||||
r"open\s*\([^)]*request",
|
||||
r"open\s*\([^)]*\+",
|
||||
r"Path\s*\([^)]*request",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -419,9 +419,9 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_unsafe_deserialization(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-020: Check for unsafe deserialization"""
|
||||
patterns = [
|
||||
(r'pickle\.loads?\s*\(', "pickle deserialization"),
|
||||
(r'yaml\.load\s*\([^,)]+\)(?!.*SafeLoader)', "yaml.load without SafeLoader"),
|
||||
(r'marshal\.loads?\s*\(', "marshal deserialization"),
|
||||
(r"pickle\.loads?\s*\(", "pickle deserialization"),
|
||||
(r"yaml\.load\s*\([^,)]+\)(?!.*SafeLoader)", "yaml.load without SafeLoader"),
|
||||
(r"marshal\.loads?\s*\(", "marshal deserialization"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -443,10 +443,10 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_pii_logging(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-021: Check for PII in logs"""
|
||||
patterns = [
|
||||
(r'log\w*\.[a-z]+\([^)]*password', "password in log"),
|
||||
(r'log\w*\.[a-z]+\([^)]*credit_card', "credit card in log"),
|
||||
(r'log\w*\.[a-z]+\([^)]*ssn', "SSN in log"),
|
||||
(r'print\s*\([^)]*password', "password in print"),
|
||||
(r"log\w*\.[a-z]+\([^)]*password", "password in log"),
|
||||
(r"log\w*\.[a-z]+\([^)]*credit_card", "credit card in log"),
|
||||
(r"log\w*\.[a-z]+\([^)]*ssn", "SSN in log"),
|
||||
(r"print\s*\([^)]*password", "password in print"),
|
||||
]
|
||||
|
||||
exclude = ["password_hash", "password_reset", "password_changed", "# noqa"]
|
||||
@@ -470,9 +470,9 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_error_leakage(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-024: Check for error information leakage"""
|
||||
patterns = [
|
||||
r'traceback\.format_exc\(\).*detail',
|
||||
r'traceback\.format_exc\(\).*response',
|
||||
r'str\(e\).*HTTPException',
|
||||
r"traceback\.format_exc\(\).*detail",
|
||||
r"traceback\.format_exc\(\).*response",
|
||||
r"str\(e\).*HTTPException",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -494,7 +494,7 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_https_enforcement(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-034: Check for HTTP instead of HTTPS"""
|
||||
for i, line in enumerate(lines, 1):
|
||||
if re.search(r'http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\$)', line):
|
||||
if re.search(r"http://(?!localhost|127\.0\.0\.1|0\.0\.0\.0|\$)", line):
|
||||
if "# noqa" in line or "example.com" in line or "schemas" in line:
|
||||
continue
|
||||
if "http://www.w3.org" in line:
|
||||
@@ -514,11 +514,11 @@ class SecurityValidator(BaseValidator):
|
||||
"""SEC-040: Check for missing timeouts on external calls"""
|
||||
# Check for requests/httpx calls without timeout
|
||||
if "requests" in content or "httpx" in content or "aiohttp" in content:
|
||||
has_timeout_import = "timeout" in content.lower()
|
||||
"timeout" in content.lower()
|
||||
|
||||
patterns = [
|
||||
r'requests\.(get|post|put|delete|patch)\s*\([^)]+\)(?!.*timeout)',
|
||||
r'httpx\.(get|post|put|delete|patch)\s*\([^)]+\)(?!.*timeout)',
|
||||
r"requests\.(get|post|put|delete|patch)\s*\([^)]+\)(?!.*timeout)",
|
||||
r"httpx\.(get|post|put|delete|patch)\s*\([^)]+\)(?!.*timeout)",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -538,10 +538,10 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_weak_hashing(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-041: Check for weak hashing algorithms"""
|
||||
patterns = [
|
||||
(r'hashlib\.md5\s*\(', "MD5"),
|
||||
(r'hashlib\.sha1\s*\(', "SHA1"),
|
||||
(r'MD5\.new\s*\(', "MD5"),
|
||||
(r'SHA\.new\s*\(', "SHA1"),
|
||||
(r"hashlib\.md5\s*\(", "MD5"),
|
||||
(r"hashlib\.sha1\s*\(", "SHA1"),
|
||||
(r"MD5\.new\s*\(", "MD5"),
|
||||
(r"SHA\.new\s*\(", "SHA1"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -572,9 +572,9 @@ class SecurityValidator(BaseValidator):
|
||||
return
|
||||
|
||||
patterns = [
|
||||
r'random\.random\s*\(',
|
||||
r'random\.randint\s*\(',
|
||||
r'random\.choice\s*\(',
|
||||
r"random\.random\s*\(",
|
||||
r"random\.randint\s*\(",
|
||||
r"random\.choice\s*\(",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -623,9 +623,9 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_certificate_verification(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-047: Check for disabled certificate verification"""
|
||||
patterns = [
|
||||
(r'verify\s*=\s*False', "SSL verification disabled"),
|
||||
(r'CERT_NONE', "Certificate verification disabled"),
|
||||
(r'check_hostname\s*=\s*False', "Hostname verification disabled"),
|
||||
(r"verify\s*=\s*False", "SSL verification disabled"),
|
||||
(r"CERT_NONE", "Certificate verification disabled"),
|
||||
(r"check_hostname\s*=\s*False", "Hostname verification disabled"),
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
@@ -665,12 +665,12 @@ class SecurityValidator(BaseValidator):
|
||||
def _check_sensitive_url_params_js(self, file_path: Path, content: str, lines: list[str]):
|
||||
"""SEC-022: Check for sensitive data in URLs (JavaScript)"""
|
||||
patterns = [
|
||||
r'\?password=',
|
||||
r'&password=',
|
||||
r'\?token=(?!type)',
|
||||
r'&token=(?!type)',
|
||||
r'\?api_key=',
|
||||
r'&api_key=',
|
||||
r"\?password=",
|
||||
r"&password=",
|
||||
r"\?token=(?!type)",
|
||||
r"&token=(?!type)",
|
||||
r"\?api_key=",
|
||||
r"&api_key=",
|
||||
]
|
||||
|
||||
for i, line in enumerate(lines, 1):
|
||||
|
||||
@@ -220,9 +220,7 @@ class BaseValidator:
|
||||
# Look for the function definition
|
||||
for j in range(i + 1, min(i + 10, len(lines))):
|
||||
next_line = lines[j].strip()
|
||||
if next_line.startswith("def ") or next_line.startswith(
|
||||
"async def "
|
||||
):
|
||||
if next_line.startswith(("def ", "async def ")):
|
||||
# Extract function name
|
||||
match = re.search(r"(?:async\s+)?def\s+(\w+)", next_line)
|
||||
if match:
|
||||
|
||||
@@ -5,10 +5,10 @@ import os
|
||||
import sys
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from alembic.config import Config
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from alembic import command
|
||||
from alembic.config import Config
|
||||
|
||||
# Add project root to Python path
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
|
||||
@@ -143,11 +143,13 @@ def verify_model_structure():
|
||||
)
|
||||
|
||||
# Import specific models
|
||||
from app.modules.inventory.models import Inventory
|
||||
from app.modules.marketplace.models import MarketplaceImportJob, MarketplaceProduct
|
||||
from app.modules.catalog.models import Product
|
||||
from app.modules.tenancy.models import User
|
||||
from app.modules.tenancy.models import Store
|
||||
from app.modules.inventory.models import Inventory
|
||||
from app.modules.marketplace.models import (
|
||||
MarketplaceImportJob,
|
||||
MarketplaceProduct,
|
||||
)
|
||||
from app.modules.tenancy.models import Store, User
|
||||
|
||||
print("[OK] All database models imported successfully")
|
||||
|
||||
@@ -227,10 +229,7 @@ if __name__ == "__main__":
|
||||
|
||||
check_project_structure()
|
||||
|
||||
if verify_model_structure():
|
||||
success = verify_database_setup()
|
||||
else:
|
||||
success = False
|
||||
success = verify_database_setup() if verify_model_structure() else False
|
||||
|
||||
if success:
|
||||
print()
|
||||
|
||||
Reference in New Issue
Block a user