diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..7bbc7a7c
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,21 @@
+.env
+.env.*
+!.env.example
+.git
+.gitea
+.gitlab-ci.yml
+__pycache__
+*.pyc
+*.pyo
+site/
+docs/
+exports/
+alembic/versions_backup/
+*.csv
+*.md
+!requirements.txt
+.pre-commit-config.yaml
+.architecture-rules/
+.performance-rules/
+.security-rules/
+mkdocs.yml
diff --git a/app/modules/customers/routes/api/storefront.py b/app/modules/customers/routes/api/storefront.py
index d10ca502..7d3c0d87 100644
--- a/app/modules/customers/routes/api/storefront.py
+++ b/app/modules/customers/routes/api/storefront.py
@@ -317,7 +317,7 @@ def forgot_password(request: Request, email: str, db: Session = Depends(get_db))
)
except Exception as e:
db.rollback()
- logger.error(f"Failed to send password reset email: {e}")
+ logger.error(f"Failed to send password reset email: {e}") # noqa: SEC-021
else:
logger.info(
f"Password reset requested for non-existent email {email} (store: {store.subdomain})"
diff --git a/app/modules/customers/services/customer_service.py b/app/modules/customers/services/customer_service.py
index 1dbc75ef..765bf904 100644
--- a/app/modules/customers/services/customer_service.py
+++ b/app/modules/customers/services/customer_service.py
@@ -570,7 +570,7 @@ class CustomerService:
# Mark token as used
token_record.mark_used(db)
- logger.info(f"Password reset completed for customer {customer.id}")
+ logger.info(f"Password reset completed for customer {customer.id}") # noqa: SEC-021
return customer
diff --git a/app/modules/customers/tests/unit/test_admin_customer_service.py b/app/modules/customers/tests/unit/test_admin_customer_service.py
index 175d5ee8..19cfc86e 100644
--- a/app/modules/customers/tests/unit/test_admin_customer_service.py
+++ b/app/modules/customers/tests/unit/test_admin_customer_service.py
@@ -36,7 +36,7 @@ def multiple_customers(db, test_store):
customer = Customer(
store_id=test_store.id,
email=f"customer{i}@example.com",
- hashed_password="hashed_password_placeholder",
+ hashed_password="hashed_password_placeholder", # noqa: SEC-001
first_name=f"First{i}",
last_name=f"Last{i}",
customer_number=f"CUST-00{i}",
diff --git a/app/modules/customers/tests/unit/test_customer_model.py b/app/modules/customers/tests/unit/test_customer_model.py
index a1c936d6..28d24e53 100644
--- a/app/modules/customers/tests/unit/test_customer_model.py
+++ b/app/modules/customers/tests/unit/test_customer_model.py
@@ -16,7 +16,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="customer@example.com",
- hashed_password="hashed_password",
+ hashed_password="hashed_password", # noqa: SEC-001
first_name="John",
last_name="Doe",
customer_number="CUST001",
@@ -40,7 +40,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="defaults@example.com",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
customer_number="CUST_DEFAULTS",
)
db.add(customer)
@@ -57,7 +57,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="fullname@example.com",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
customer_number="CUST_FULLNAME",
first_name="Jane",
last_name="Smith",
@@ -73,7 +73,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="noname@example.com",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
customer_number="CUST_NONAME",
)
db.add(customer)
@@ -87,7 +87,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="optional@example.com",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
customer_number="CUST_OPT",
phone="+352123456789",
preferences={"language": "en", "currency": "EUR"},
@@ -106,7 +106,7 @@ class TestCustomerModel:
customer = Customer(
store_id=test_store.id,
email="relationship@example.com",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
customer_number="CUST_REL",
)
db.add(customer)
diff --git a/app/modules/customers/tests/unit/test_customer_schema.py b/app/modules/customers/tests/unit/test_customer_schema.py
index c2d7cded..8fb10c28 100644
--- a/app/modules/customers/tests/unit/test_customer_schema.py
+++ b/app/modules/customers/tests/unit/test_customer_schema.py
@@ -24,7 +24,7 @@ class TestCustomerRegisterSchema:
"""Test valid registration data."""
customer = CustomerRegister(
email="customer@example.com",
- password="Password123",
+ password="Password123", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -36,7 +36,7 @@ class TestCustomerRegisterSchema:
"""Test email is normalized to lowercase."""
customer = CustomerRegister(
email="Customer@Example.COM",
- password="Password123",
+ password="Password123", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -47,7 +47,7 @@ class TestCustomerRegisterSchema:
with pytest.raises(ValidationError) as exc_info:
CustomerRegister(
email="not-an-email",
- password="Password123",
+ password="Password123", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -58,7 +58,7 @@ class TestCustomerRegisterSchema:
with pytest.raises(ValidationError) as exc_info:
CustomerRegister(
email="customer@example.com",
- password="Pass1",
+ password="Pass1", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -69,7 +69,7 @@ class TestCustomerRegisterSchema:
with pytest.raises(ValidationError) as exc_info:
CustomerRegister(
email="customer@example.com",
- password="Password",
+ password="Password", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -80,7 +80,7 @@ class TestCustomerRegisterSchema:
with pytest.raises(ValidationError) as exc_info:
CustomerRegister(
email="customer@example.com",
- password="12345678",
+ password="12345678", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -91,7 +91,7 @@ class TestCustomerRegisterSchema:
with pytest.raises(ValidationError) as exc_info:
CustomerRegister(
email="customer@example.com",
- password="Password123",
+ password="Password123", # noqa: SEC-001
last_name="Doe",
)
assert "first_name" in str(exc_info.value).lower()
@@ -100,7 +100,7 @@ class TestCustomerRegisterSchema:
"""Test marketing_consent defaults to False."""
customer = CustomerRegister(
email="customer@example.com",
- password="Password123",
+ password="Password123", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -110,7 +110,7 @@ class TestCustomerRegisterSchema:
"""Test optional phone field."""
customer = CustomerRegister(
email="customer@example.com",
- password="Password123",
+ password="Password123", # noqa: SEC-001
first_name="John",
last_name="Doe",
phone="+352 123 456",
diff --git a/app/modules/messaging/static/admin/js/email-templates.js b/app/modules/messaging/static/admin/js/email-templates.js
index f8791fa0..176f7d8f 100644
--- a/app/modules/messaging/static/admin/js/email-templates.js
+++ b/app/modules/messaging/static/admin/js/email-templates.js
@@ -224,7 +224,7 @@ function emailTemplatesPage() {
},
'password_reset': {
customer_name: 'John Doe',
- reset_link: 'https://example.com/reset?token=abc123',
+ reset_link: 'https://example.com/reset?token=abc123', // # noqa: SEC-022
expiry_hours: '1'
},
'team_invite': {
diff --git a/app/modules/messaging/tests/unit/test_store_email_settings_service.py b/app/modules/messaging/tests/unit/test_store_email_settings_service.py
index 0ff14aa3..38bcec20 100644
--- a/app/modules/messaging/tests/unit/test_store_email_settings_service.py
+++ b/app/modules/messaging/tests/unit/test_store_email_settings_service.py
@@ -33,7 +33,7 @@ def test_email_settings(db, test_store):
smtp_host="smtp.example.com",
smtp_port=587,
smtp_username="testuser",
- smtp_password="testpass",
+ smtp_password="testpass", # noqa: SEC-001
smtp_use_tls=True,
smtp_use_ssl=False,
is_configured=True,
@@ -56,7 +56,7 @@ def test_verified_email_settings(db, test_store):
smtp_host="smtp.example.com",
smtp_port=587,
smtp_username="testuser",
- smtp_password="testpass",
+ smtp_password="testpass", # noqa: SEC-001
smtp_use_tls=True,
is_configured=True,
is_verified=True,
@@ -155,7 +155,7 @@ class TestStoreEmailSettingsWrite:
"smtp_host": "smtp.example.com",
"smtp_port": 587,
"smtp_username": "user",
- "smtp_password": "pass",
+ "smtp_password": "pass", # noqa: SEC-001
}
settings = store_email_settings_service.create_or_update(
diff --git a/app/modules/monitoring/static/admin/js/logs.js b/app/modules/monitoring/static/admin/js/logs.js
index ddeb5631..544033ff 100644
--- a/app/modules/monitoring/static/admin/js/logs.js
+++ b/app/modules/monitoring/static/admin/js/logs.js
@@ -197,7 +197,7 @@ function adminLogs() {
const token = localStorage.getItem('admin_token');
// Note: window.open bypasses apiClient, so we need the full path
const url = `/api/v1/admin/logs/files/${this.selectedFile}/download`;
- window.open(`${url}?token=${token}`, '_blank'); // noqa: sec-022
+ window.open(`${url}?token=${token}`, '_blank'); // # noqa: SEC-022
} catch (error) {
logsLog.error('Failed to download log file:', error);
this.error = 'Failed to download log file';
diff --git a/app/modules/tenancy/tests/unit/test_admin_platform_service.py b/app/modules/tenancy/tests/unit/test_admin_platform_service.py
index 637ab122..a412a5e9 100644
--- a/app/modules/tenancy/tests/unit/test_admin_platform_service.py
+++ b/app/modules/tenancy/tests/unit/test_admin_platform_service.py
@@ -255,7 +255,7 @@ class TestAdminPlatformServiceQueries:
another_admin = User(
email="another_padmin@example.com",
username="another_padmin",
- hashed_password=auth_manager.hash_password("pass"),
+ hashed_password=auth_manager.hash_password("pass"), # noqa: SEC-001
role="admin",
is_active=True,
is_super_admin=False,
@@ -342,7 +342,7 @@ class TestAdminPlatformServiceSuperAdmin:
another_super = User(
email="another_super@example.com",
username="another_super",
- hashed_password=auth_manager.hash_password("pass"),
+ hashed_password=auth_manager.hash_password("pass"), # noqa: SEC-001
role="admin",
is_active=True,
is_super_admin=True,
@@ -416,7 +416,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
db=db,
email="new_padmin@example.com",
username="new_padmin",
- password="securepass123",
+ password="securepass123", # noqa: SEC-001
platform_ids=[test_platform.id, another_platform.id],
created_by_user_id=test_super_admin.id,
first_name="New",
@@ -444,7 +444,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
db=db,
email=test_platform_admin.email, # Duplicate
username="unique_username",
- password="securepass123",
+ password="securepass123", # noqa: SEC-001
platform_ids=[test_platform.id],
created_by_user_id=test_super_admin.id,
)
@@ -461,7 +461,7 @@ class TestAdminPlatformServiceCreatePlatformAdmin:
db=db,
email="unique@example.com",
username=test_platform_admin.username, # Duplicate
- password="securepass123",
+ password="securepass123", # noqa: SEC-001
platform_ids=[test_platform.id],
created_by_user_id=test_super_admin.id,
)
diff --git a/app/modules/tenancy/tests/unit/test_store_team_service.py b/app/modules/tenancy/tests/unit/test_store_team_service.py
index 10b4ec85..1fad399d 100644
--- a/app/modules/tenancy/tests/unit/test_store_team_service.py
+++ b/app/modules/tenancy/tests/unit/test_store_team_service.py
@@ -87,7 +87,7 @@ def pending_invitation(db, team_store, test_user, auth_manager):
new_user = User(
email=f"pending_{unique_id}@example.com",
username=f"pending_{unique_id}",
- hashed_password=auth_manager.hash_password("temppass"),
+ hashed_password=auth_manager.hash_password("temppass"), # noqa: SEC-001
role="store",
is_active=False,
)
@@ -129,7 +129,7 @@ def expired_invitation(db, team_store, test_user, auth_manager):
new_user = User(
email=f"expired_{unique_id}@example.com",
username=f"expired_{unique_id}",
- hashed_password=auth_manager.hash_password("temppass"),
+ hashed_password=auth_manager.hash_password("temppass"), # noqa: SEC-001
role="store",
is_active=False,
)
@@ -186,7 +186,7 @@ class TestStoreTeamServiceAccept:
result = store_team_service.accept_invitation(
db=db,
invitation_token=pending_invitation.invitation_token,
- password="newpassword123",
+ password="newpassword123", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
@@ -203,7 +203,7 @@ class TestStoreTeamServiceAccept:
store_team_service.accept_invitation(
db=db,
invitation_token="invalid_token_12345",
- password="password123",
+ password="password123", # noqa: SEC-001
)
def test_accept_invitation_already_accepted(self, db, team_member):
@@ -213,7 +213,7 @@ class TestStoreTeamServiceAccept:
store_team_service.accept_invitation(
db=db,
invitation_token="some_token", # team_member has no token
- password="password123",
+ password="password123", # noqa: SEC-001
)
def test_accept_invitation_expired(self, db, expired_invitation):
@@ -222,7 +222,7 @@ class TestStoreTeamServiceAccept:
store_team_service.accept_invitation(
db=db,
invitation_token=expired_invitation.invitation_token,
- password="password123",
+ password="password123", # noqa: SEC-001
)
assert "expired" in str(exc_info.value).lower()
diff --git a/app/modules/tenancy/tests/unit/test_user_model.py b/app/modules/tenancy/tests/unit/test_user_model.py
index 56ec7619..d9c0dc26 100644
--- a/app/modules/tenancy/tests/unit/test_user_model.py
+++ b/app/modules/tenancy/tests/unit/test_user_model.py
@@ -17,7 +17,7 @@ class TestUserModel:
user = User(
email="db_test@example.com",
username="dbtest",
- hashed_password="hashed_password_123",
+ hashed_password="hashed_password_123", # noqa: SEC-001
role="user",
is_active=True,
)
@@ -39,7 +39,7 @@ class TestUserModel:
user1 = User(
email="unique@example.com",
username="user1",
- hashed_password="hash1",
+ hashed_password="hash1", # noqa: SEC-001
)
db.add(user1)
db.commit()
@@ -49,7 +49,7 @@ class TestUserModel:
user2 = User(
email="unique@example.com",
username="user2",
- hashed_password="hash2",
+ hashed_password="hash2", # noqa: SEC-001
)
db.add(user2)
db.commit()
@@ -59,7 +59,7 @@ class TestUserModel:
user1 = User(
email="user1@example.com",
username="sameusername",
- hashed_password="hash1",
+ hashed_password="hash1", # noqa: SEC-001
)
db.add(user1)
db.commit()
@@ -69,7 +69,7 @@ class TestUserModel:
user2 = User(
email="user2@example.com",
username="sameusername",
- hashed_password="hash2",
+ hashed_password="hash2", # noqa: SEC-001
)
db.add(user2)
db.commit()
@@ -79,7 +79,7 @@ class TestUserModel:
user = User(
email="defaults@example.com",
username="defaultuser",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
)
db.add(user)
db.commit()
@@ -93,7 +93,7 @@ class TestUserModel:
user = User(
email="optional@example.com",
username="optionaluser",
- hashed_password="hash",
+ hashed_password="hash", # noqa: SEC-001
first_name="John",
last_name="Doe",
)
diff --git a/conftest.py b/conftest.py
index 4820a03b..8ad4e585 100644
--- a/conftest.py
+++ b/conftest.py
@@ -128,7 +128,7 @@ def db(engine, testing_session_local):
# Disable FK checks temporarily for fast truncation
conn.execute(text("SET session_replication_role = 'replica'"))
for table in reversed(Base.metadata.sorted_tables):
- conn.execute(text(f'TRUNCATE TABLE "{table.name}" CASCADE'))
+ conn.execute(text(f'TRUNCATE TABLE "{table.name}" CASCADE')) # noqa: SEC-011
conn.execute(text("SET session_replication_role = 'origin'"))
conn.commit()
diff --git a/docker-compose.yml b/docker-compose.yml
index 367ec500..efb7506d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -36,6 +36,7 @@ services:
- full # Only start with: docker compose --profile full up -d
ports:
- "8001:8000" # Use 8001 to avoid conflict with local dev server
+ env_file: .env
environment:
DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
JWT_SECRET_KEY: ${JWT_SECRET_KEY:-your-super-secret-key}
@@ -62,6 +63,7 @@ services:
profiles:
- full # Only start with: docker compose --profile full up -d
command: celery -A app.core.celery_config worker --loglevel=info -Q default,long_running,scheduled
+ env_file: .env
environment:
DATABASE_URL: postgresql://orion_user:secure_password@db:5432/orion_db
REDIS_URL: redis://redis:6379/0
diff --git a/scripts/seed/install.py b/scripts/seed/install.py
index ad383d8a..2e3fda39 100755
--- a/scripts/seed/install.py
+++ b/scripts/seed/install.py
@@ -576,10 +576,10 @@ def main():
admin_email = env_vars.get("ADMIN_EMAIL", "admin@orion.lu")
print(" URL: /admin/login")
print(f" Email: {admin_email}")
- print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}")
+ print(f" Password: {'(configured in .env)' if env_vars.get('ADMIN_PASSWORD') else 'admin123'}") # noqa: SEC-021
if not env_vars.get("ADMIN_PASSWORD"):
- print(f"\n {Colors.WARNING}ā CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}")
+ print(f"\n {Colors.WARNING}ā CHANGE DEFAULT PASSWORD IMMEDIATELY!{Colors.ENDC}") # noqa: SEC-021
print(f"\n {Colors.BOLD}For demo data (development only):{Colors.ENDC}")
print(" make seed-demo")
diff --git a/scripts/seed/seed_demo.py b/scripts/seed/seed_demo.py
index 8d497fe6..f8cb8149 100644
--- a/scripts/seed/seed_demo.py
+++ b/scripts/seed/seed_demo.py
@@ -103,7 +103,7 @@ DEMO_COMPANIES = [
"name": "WizaCorp Ltd.",
"description": "Leading technology and electronics distributor",
"owner_email": "john.owner@wizacorp.com",
- "owner_password": "password123",
+ "owner_password": "password123", # noqa: SEC-001
"owner_first_name": "John",
"owner_last_name": "Smith",
"contact_email": "info@wizacorp.com",
@@ -116,7 +116,7 @@ DEMO_COMPANIES = [
"name": "Fashion Group S.A.",
"description": "International fashion and lifestyle retailer",
"owner_email": "jane.owner@fashiongroup.com",
- "owner_password": "password123",
+ "owner_password": "password123", # noqa: SEC-001
"owner_first_name": "Jane",
"owner_last_name": "Merchant",
"contact_email": "contact@fashiongroup.com",
@@ -129,7 +129,7 @@ DEMO_COMPANIES = [
"name": "BookWorld Publishing",
"description": "Books, education, and media content provider",
"owner_email": "bob.owner@bookworld.com",
- "owner_password": "password123",
+ "owner_password": "password123", # noqa: SEC-001
"owner_first_name": "Bob",
"owner_last_name": "Seller",
"contact_email": "support@bookworld.com",
@@ -213,7 +213,7 @@ DEMO_TEAM_MEMBERS = [
{
"merchant_index": 0,
"email": "alice.manager@wizacorp.com",
- "password": "password123",
+ "password": "password123", # noqa: SEC-001
"first_name": "Alice",
"last_name": "Manager",
"store_codes": ["ORION", "WIZAGADGETS"], # manages two stores
@@ -222,7 +222,7 @@ DEMO_TEAM_MEMBERS = [
{
"merchant_index": 0,
"email": "charlie.staff@wizacorp.com",
- "password": "password123",
+ "password": "password123", # noqa: SEC-001
"first_name": "Charlie",
"last_name": "Staff",
"store_codes": ["WIZAHOME"],
@@ -232,7 +232,7 @@ DEMO_TEAM_MEMBERS = [
{
"merchant_index": 1,
"email": "diana.stylist@fashiongroup.com",
- "password": "password123",
+ "password": "password123", # noqa: SEC-001
"first_name": "Diana",
"last_name": "Stylist",
"store_codes": ["FASHIONHUB", "FASHIONOUTLET"],
@@ -241,7 +241,7 @@ DEMO_TEAM_MEMBERS = [
{
"merchant_index": 1,
"email": "eric.sales@fashiongroup.com",
- "password": "password123",
+ "password": "password123", # noqa: SEC-001
"first_name": "Eric",
"last_name": "Sales",
"store_codes": ["FASHIONOUTLET"],
@@ -251,7 +251,7 @@ DEMO_TEAM_MEMBERS = [
{
"merchant_index": 2,
"email": "fiona.editor@bookworld.com",
- "password": "password123",
+ "password": "password123", # noqa: SEC-001
"first_name": "Fiona",
"last_name": "Editor",
"store_codes": ["BOOKSTORE", "BOOKDIGITAL"],
@@ -615,7 +615,7 @@ def create_demo_merchants(db: Session, auth_manager: AuthManager) -> list[Mercha
owner_user = User(
username=merchant_data["owner_email"].split("@")[0],
email=merchant_data["owner_email"],
- hashed_password=auth_manager.hash_password(
+ hashed_password=auth_manager.hash_password( # noqa: SEC-001
merchant_data["owner_password"]
),
role="store",
@@ -780,7 +780,7 @@ def create_demo_team_members(
user = User(
username=member_data["email"].split("@")[0],
email=member_data["email"],
- hashed_password=auth_manager.hash_password(member_data["password"]),
+ hashed_password=auth_manager.hash_password(member_data["password"]), # noqa: SEC-001
role="store",
first_name=member_data["first_name"],
last_name=member_data["last_name"],
@@ -838,7 +838,7 @@ def create_demo_customers(
customers = []
# Use a simple demo password for all customers
- demo_password = "customer123"
+ demo_password = "customer123" # noqa: SEC-001
for i in range(1, count + 1):
email = f"customer{i}@{store.subdomain}.example.com"
@@ -858,7 +858,7 @@ def create_demo_customers(
customer = Customer(
store_id=store.id,
email=email,
- hashed_password=auth_manager.hash_password(demo_password),
+ hashed_password=auth_manager.hash_password(demo_password), # noqa: SEC-001
first_name=f"Customer{i}",
last_name="Test",
phone=f"+352123456{i:03d}",
@@ -1178,7 +1178,7 @@ def print_summary(db: Session):
merchant = merchants[i - 1] if i <= len(merchants) else None
print(f" Merchant {i}: {merchant_data['name']}")
print(f" Email: {merchant_data['owner_email']}")
- print(f" Password: {merchant_data['owner_password']}")
+ print(f" Password: {merchant_data['owner_password']}") # noqa: SEC-021
if merchant and merchant.stores:
for store in merchant.stores:
print(
@@ -1196,7 +1196,7 @@ def print_summary(db: Session):
store_codes = ", ".join(member_data["store_codes"])
print(f" {member_data['first_name']} {member_data['last_name']} ({merchant_name})")
print(f" Email: {member_data['email']}")
- print(f" Password: {member_data['password']}")
+ print(f" Password: {member_data['password']}") # noqa: SEC-021
print(f" Stores: {store_codes}")
print()
@@ -1204,7 +1204,7 @@ def print_summary(db: Session):
print("ā" * 70)
print(" All customers:")
print(" Email: customer1@{subdomain}.example.com")
- print(" Password: customer123")
+ print(" Password: customer123") # noqa: SEC-021
print(" (Replace {subdomain} with store subdomain, e.g., orion)")
print()
@@ -1228,7 +1228,7 @@ def print_summary(db: Session):
print(" 3. Visit store shop: http://localhost:8000/stores/ORION/shop/")
print(" 4. Admin panel: http://localhost:8000/admin/login")
print(f" Username: {settings.admin_username}")
- print(f" Password: {settings.admin_password}")
+ print(f" Password: {settings.admin_password}") # noqa: SEC-021
# =============================================================================
diff --git a/scripts/test_store_management.py b/scripts/test_store_management.py
index 60f69e94..5d88f692 100644
--- a/scripts/test_store_management.py
+++ b/scripts/test_store_management.py
@@ -72,7 +72,7 @@ def test_create_store_with_both_emails():
print(f" Contact Email: {data['contact_email']}")
print("\nš Credentials:")
print(f" Username: {data['owner_username']}")
- print(f" Password: {data['temporary_password']}")
+ print(f" Password: {data['temporary_password']}") # noqa: SEC-021
print(f"\nš Login URL: {data['login_url']}")
return data["id"]
print(f"ā Failed: {response.status_code}")
diff --git a/scripts/validate/base_validator.py b/scripts/validate/base_validator.py
index 139fe60b..9a860173 100755
--- a/scripts/validate/base_validator.py
+++ b/scripts/validate/base_validator.py
@@ -4,6 +4,7 @@ Base Validator Class
Shared functionality for all validators.
"""
+import re
from abc import ABC
from dataclasses import dataclass, field
from enum import Enum
@@ -62,8 +63,18 @@ class BaseValidator(ABC):
".venv", "venv", "node_modules", "__pycache__", ".git",
".pytest_cache", ".mypy_cache", "dist", "build", "*.egg-info",
"migrations", "alembic/versions", ".tox", "htmlcov",
+ "site", # mkdocs build output
]
+ # Regex for noqa comments: # noqa, # noqa: RULE-001, # noqa: RULE-001, RULE-002
+ _NOQA_PATTERN = re.compile(
+ r"#\s*noqa(?::\s*([A-Z]+-\d+(?:\s*,\s*[A-Z]+-\d+)*))?",
+ )
+ # Same for HTML comments:
+ _NOQA_HTML_PATTERN = re.compile(
+ r"",
+ )
+
def __init__(
self,
rules_dir: str = "",
@@ -180,6 +191,26 @@ class BaseValidator(ABC):
path_str = str(file_path)
return any(pattern in path_str for pattern in self.IGNORE_PATTERNS)
+ def _is_noqa_suppressed(self, line: str, rule_id: str) -> bool:
+ """Check if a line has a noqa comment suppressing the given rule.
+
+ Supports:
+ - ``# noqa`` ā suppresses all rules
+ - ``# noqa: SEC-001`` ā suppresses specific rule
+ - ``# noqa: SEC-001, SEC-002`` ā suppresses multiple rules
+ - ```` ā HTML comment variant
+ """
+ for pattern in (self._NOQA_PATTERN, self._NOQA_HTML_PATTERN):
+ match = pattern.search(line)
+ if match:
+ rule_list = match.group(1)
+ if not rule_list:
+ return True # bare # noqa ā suppress everything
+ suppressed = [r.strip() for r in rule_list.split(",")]
+ if rule_id in suppressed:
+ return True
+ return False
+
def _add_violation(
self,
rule_id: str,
diff --git a/scripts/validate/validate_audit.py b/scripts/validate/validate_audit.py
index d4d864c9..d7648d53 100644
--- a/scripts/validate/validate_audit.py
+++ b/scripts/validate/validate_audit.py
@@ -196,7 +196,7 @@ class AuditValidator(BaseValidator):
r"logger\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
r"logger\.\w+\(.*password\s*[=:]\s*\{", # password={var}
r"logging\.\w+\(.*password\s*[=:]\s*['\"]?%", # password=%s
- r"print\(.*password\s*=", # print(password=xxx)
+ r"print\(.*password\s*=", # print(password=xxx) # noqa: SEC-021
r"logger.*credit.*card.*\d", # credit card with numbers
r"logger.*\bssn\b.*\d", # SSN with numbers
],
diff --git a/scripts/validate/validate_performance.py b/scripts/validate/validate_performance.py
index 348b79fa..c0241817 100755
--- a/scripts/validate/validate_performance.py
+++ b/scripts/validate/validate_performance.py
@@ -199,6 +199,8 @@ class PerformanceValidator(BaseValidator):
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."]):
+ if self._is_noqa_suppressed(line, "PERF-001"):
+ continue
self._add_violation(
rule_id="PERF-001",
rule_name="N+1 query detection",
@@ -225,7 +227,7 @@ class PerformanceValidator(BaseValidator):
context_text = "\n".join(context_lines)
if "limit" not in context_text.lower() and "filter" not in context_text.lower():
- if "# noqa" in line or "# bounded" in line:
+ if self._is_noqa_suppressed(line, "PERF-003") or "# bounded" in line:
continue
self._add_violation(
rule_id="PERF-003",
@@ -256,6 +258,8 @@ class PerformanceValidator(BaseValidator):
if current_indent <= for_indent and stripped:
in_for_loop = False
elif "db.add(" in line or ".save(" in line:
+ if self._is_noqa_suppressed(line, "PERF-006"):
+ continue
self._add_violation(
rule_id="PERF-006",
rule_name="Bulk operations for multiple records",
@@ -278,6 +282,8 @@ class PerformanceValidator(BaseValidator):
for i, line in enumerate(lines, 1):
for pattern, issue in patterns:
if re.search(pattern, line):
+ if self._is_noqa_suppressed(line, "PERF-008"):
+ continue
self._add_violation(
rule_id="PERF-008",
rule_name="Use EXISTS for existence checks",
@@ -311,17 +317,18 @@ class PerformanceValidator(BaseValidator):
in_for_loop = False
elif loop_var and f"{loop_var}." in line and "=" in line and "==" not in line:
# Attribute assignment in loop
- if "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-009",
- rule_name="Batch updates instead of loops",
- severity=Severity.INFO,
- file_path=file_path,
- line_number=i,
- message="Individual updates in loop - consider batch update",
- context=line.strip()[:80],
- suggestion="Use .update({...}) with filters for batch updates",
- )
+ if self._is_noqa_suppressed(line, "PERF-009"):
+ continue
+ self._add_violation(
+ rule_id="PERF-009",
+ rule_name="Batch updates instead of loops",
+ severity=Severity.INFO,
+ file_path=file_path,
+ line_number=i,
+ message="Individual updates in loop - consider batch update",
+ context=line.strip()[:80],
+ suggestion="Use .update({...}) with filters for batch updates",
+ )
# =========================================================================
# API Performance Checks
@@ -349,17 +356,18 @@ class PerformanceValidator(BaseValidator):
in_endpoint = False
# Check for .all() without pagination
if ".all()" in line and not has_pagination:
- if "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-026",
- rule_name="Pagination required for list endpoints",
- severity=Severity.WARNING,
- file_path=file_path,
- line_number=i,
- message="List endpoint may lack pagination",
- context=line.strip()[:80],
- suggestion="Add skip/limit parameters for pagination",
- )
+ if self._is_noqa_suppressed(line, "PERF-026"):
+ continue
+ self._add_violation(
+ rule_id="PERF-026",
+ rule_name="Pagination required for list endpoints",
+ severity=Severity.WARNING,
+ file_path=file_path,
+ line_number=i,
+ message="List endpoint may lack pagination",
+ context=line.strip()[:80],
+ suggestion="Add skip/limit parameters for pagination",
+ )
# =========================================================================
# Async Performance Checks
@@ -381,6 +389,10 @@ class PerformanceValidator(BaseValidator):
if await_count >= 3:
# Verify they're sequential (within 5 lines of each other)
if all(await_lines[j+1] - await_lines[j] <= 2 for j in range(len(await_lines)-1)):
+ if self._is_noqa_suppressed(line, "PERF-037"):
+ await_count = 0
+ await_lines = []
+ continue
self._add_violation(
rule_id="PERF-037",
rule_name="Parallel independent operations",
@@ -412,6 +424,8 @@ class PerformanceValidator(BaseValidator):
for i, line in enumerate(lines, 1):
for pattern in patterns:
if re.search(pattern, line) and "timeout" not in line:
+ if self._is_noqa_suppressed(line, "PERF-040"):
+ continue
self._add_violation(
rule_id="PERF-040",
rule_name="Timeout configuration",
@@ -436,22 +450,25 @@ class PerformanceValidator(BaseValidator):
if i < len(lines):
next_lines = "\n".join(lines[i:min(i+3, len(lines))])
if "for " in next_lines and "in" in next_lines:
- if "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-046",
- rule_name="Generators for large datasets",
- severity=Severity.INFO,
- file_path=file_path,
- line_number=i,
- message=".all() loads everything into memory before iteration",
- context=line.strip()[:80],
- suggestion="Use .yield_per(100) for large result sets",
- )
+ if self._is_noqa_suppressed(line, "PERF-046"):
+ continue
+ self._add_violation(
+ rule_id="PERF-046",
+ rule_name="Generators for large datasets",
+ severity=Severity.INFO,
+ file_path=file_path,
+ line_number=i,
+ message=".all() loads everything into memory before iteration",
+ context=line.strip()[:80],
+ suggestion="Use .yield_per(100) for large result sets",
+ )
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 self._is_noqa_suppressed(line, "PERF-047"):
+ continue
self._add_violation(
rule_id="PERF-047",
rule_name="Stream large file uploads",
@@ -468,6 +485,9 @@ class PerformanceValidator(BaseValidator):
if "chunk" not in content.lower() and "batch" not in content.lower():
# Check if file processes multiple records
if "for " in content and ("csv" in content.lower() or "import" in content.lower()):
+ first_line = lines[0] if lines else ""
+ if self._is_noqa_suppressed(first_line, "PERF-048"):
+ return
self._add_violation(
rule_id="PERF-048",
rule_name="Chunked processing for imports",
@@ -484,17 +504,18 @@ class PerformanceValidator(BaseValidator):
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 "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-049",
- rule_name="Context managers for resources",
- severity=Severity.WARNING,
- file_path=file_path,
- line_number=i,
- message="File opened without context manager",
- context=line.strip()[:80],
- suggestion="Use 'with open(...) as f:' to ensure cleanup",
- )
+ if self._is_noqa_suppressed(line, "PERF-049"):
+ continue
+ self._add_violation(
+ rule_id="PERF-049",
+ rule_name="Context managers for resources",
+ severity=Severity.WARNING,
+ file_path=file_path,
+ line_number=i,
+ message="File opened without context manager",
+ context=line.strip()[:80],
+ suggestion="Use 'with open(...) as f:' to ensure cleanup",
+ )
def _check_string_concatenation(self, file_path: Path, content: str, lines: list[str]):
"""PERF-051: Check for inefficient string concatenation in loops"""
@@ -513,17 +534,18 @@ class PerformanceValidator(BaseValidator):
if current_indent <= for_indent and stripped:
in_for_loop = False
elif re.search(r'\w+\s*\+=\s*["\']|str\s*\(', line):
- if "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-051",
- rule_name="String concatenation efficiency",
- severity=Severity.INFO,
- file_path=file_path,
- line_number=i,
- message="String concatenation in loop",
- context=line.strip()[:80],
- suggestion="Use ''.join() or StringIO for many concatenations",
- )
+ if self._is_noqa_suppressed(line, "PERF-051"):
+ continue
+ self._add_violation(
+ rule_id="PERF-051",
+ rule_name="String concatenation efficiency",
+ severity=Severity.INFO,
+ file_path=file_path,
+ line_number=i,
+ message="String concatenation in loop",
+ context=line.strip()[:80],
+ suggestion="Use ''.join() or StringIO for many concatenations",
+ )
# =========================================================================
# Frontend Performance Checks
@@ -534,6 +556,8 @@ class PerformanceValidator(BaseValidator):
for i, line in enumerate(lines, 1):
if re.search(r'@(input|keyup)=".*search.*fetch', line, re.IGNORECASE):
if "debounce" not in content.lower():
+ if self._is_noqa_suppressed(line, "PERF-056"):
+ continue
self._add_violation(
rule_id="PERF-056",
rule_name="Debounce search inputs",
@@ -552,17 +576,18 @@ class PerformanceValidator(BaseValidator):
if match:
interval = int(match.group(1))
if interval < 10000: # Less than 10 seconds
- if "# real-time" not in line and "# noqa" not in line:
- self._add_violation(
- rule_id="PERF-062",
- rule_name="Reasonable polling intervals",
- severity=Severity.WARNING,
- file_path=file_path,
- line_number=i,
- message=f"Polling interval {interval}ms is very frequent",
- context=line.strip()[:80],
- suggestion="Use >= 10 second intervals for non-critical updates",
- )
+ if "# real-time" in line or self._is_noqa_suppressed(line, "PERF-062"):
+ continue
+ self._add_violation(
+ rule_id="PERF-062",
+ rule_name="Reasonable polling intervals",
+ severity=Severity.WARNING,
+ file_path=file_path,
+ line_number=i,
+ message=f"Polling interval {interval}ms is very frequent",
+ context=line.strip()[:80],
+ suggestion="Use >= 10 second intervals for non-critical updates",
+ )
def _check_layout_thrashing(self, file_path: Path, content: str, lines: list[str]):
"""PERF-064: Check for layout thrashing patterns"""
@@ -572,6 +597,8 @@ class PerformanceValidator(BaseValidator):
if i < len(lines):
next_line = lines[i] if i < len(lines) else ""
if "style" in next_line:
+ if self._is_noqa_suppressed(line, "PERF-064"):
+ continue
self._add_violation(
rule_id="PERF-064",
rule_name="Avoid layout thrashing",
@@ -589,6 +616,8 @@ class PerformanceValidator(BaseValidator):
if re.search(r"
]*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():
+ if self._is_noqa_suppressed(line, "PERF-058"):
+ continue
self._add_violation(
rule_id="PERF-058",
rule_name="Image optimization",
@@ -606,6 +635,8 @@ class PerformanceValidator(BaseValidator):
if re.search(r"