-
-
Notifications
You must be signed in to change notification settings - Fork 199
WIP: Implementation of management command. #2073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
WIP: Implementation of management command. #2073
Conversation
Summary by CodeRabbit
WalkthroughAdds project-level compliance detection and enforcement: new Project.project_level_official field and is_level_compliant property, management commands to fetch/apply official levels and detect compliance, score penalty application for non-compliant projects with configurable weight, supporting migrations, Makefile target, and comprehensive tests. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Assessment against linked issues
Possibly related PRs
Suggested reviewers
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. ✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 19
🧹 Nitpick comments (23)
backend/apps/owasp/models/project_health_metrics.py (1)
46-50
: Default=True can mask non-compliance until the detection job runs; ensure job ordering and consider indexing
- With default=True, new metrics won’t get penalized unless the compliance detection job has already flipped the flag. Since owasp_update_project_health_scores only computes scores where score IS NULL, running scoring before detection can permanently skip penalties for that day’s rows.
- Ensure the pipeline runs “detect compliance” before “update scores” every time.
- If you’ll frequently filter on this flag (e.g., reports, dashboards), add a DB index.
Proposed index (requires a follow-up migration):
- is_level_compliant = models.BooleanField( + is_level_compliant = models.BooleanField( verbose_name="Is project level compliant", default=True, help_text="Whether the project's local level matches the official OWASP level", + db_index=True, )Would you like me to add a combined Makefile target that enforces detect-before-score ordering?
backend/apps/owasp/Makefile (2)
64-66
: Mark new target as .PHONY to avoid filename collisionsDeclare the target as phony to prevent unexpected behavior if a file with the same name exists.
- + .PHONY: owasp-detect-project-level-compliance owasp-detect-project-level-compliance: @echo "Detecting OWASP project level compliance" @CMD="python manage.py owasp_detect_project_level_compliance" $(MAKE) exec-backend-command
64-66
: Add an orchestrator target to enforce detect-before-score orderingTo ensure penalties are applied correctly, add a convenience target that runs detection before scoring.
Example (add anywhere appropriate in this Makefile):
.PHONY: owasp-refresh-health-scores owasp-refresh-health-scores: @echo "Refreshing OWASP health scores (detect compliance -> update scores)" @$(MAKE) owasp-detect-project-level-compliance @$(MAKE) owasp-update-project-health-scoresbackend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
66-69
: Fix Ruff E501: wrap the long warning string across linesThis satisfies the line-length rule and improves readability.
- self.stdout.write( - self.style.WARNING( - f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " - f"(penalty: {penalty_amount:.2f}, final score: {score:.2f})" - ) - ) + self.stdout.write( + self.style.WARNING( + f"Applied {penalty_percentage}% compliance penalty to " + f"{metric.project.name} (penalty: {penalty_amount:.2f}, " + f"final score: {score:.2f})" + ) + )backend/apps/owasp/migrations/0047_add_is_level_compliant_field.py (1)
12-18
: Migration adds a NOT NULL boolean with default; consider operational impact on large tablesThis is correct and matches the model, but adding a NOT NULL column with a default can rewrite the entire table on some RDBMS, which may be heavy for large datasets.
If table size is large, consider a two-step approach in future:
- Add nullable column without default.
- Backfill in a separate migration/command.
- Then set default=True and NOT NULL in a final migration.
backend/apps/owasp/models/project_health_requirements.py (1)
60-64
: Consider DecimalField for user-facing percentagesIf you expect admins to set this via the admin UI and want to avoid float rounding artifacts, a DecimalField with suitable precision (e.g., max_digits=5, decimal_places=2) can be safer.
backend/apps/owasp/utils/project_level_fetcher.py (6)
5-6
: Drop deprecated typing.Dict; prefer built-in genericsRemove the import and use dict[...] in annotations.
-import json import logging -from typing import Dict +import jsonIf you switch to response.json() (see below), you can drop the json import entirely and adjust the exception handling.
15-15
: Use built-in generics and keep return type preciseSwitch to dict[str, str] | None.
-def fetch_official_project_levels(timeout: int = 30) -> Dict[str, str] | None: +def fetch_official_project_levels(timeout: int = 30) -> dict[str, str] | None:
31-36
: Use logger.error for non-exceptional invalid format, not logger.exceptionlogger.exception is intended within an except block; using it here adds a misleading traceback.
- if not isinstance(data, list): - logger.exception( - "Invalid project levels data format", - extra={"expected": "list", "got": type(data).__name__} - ) + if not isinstance(data, list): + logger.error( + "Invalid project levels data format", + extra={"expected": "list", "got": type(data).__name__}, + ) return None
28-57
: Whitespace/style nits flagged by RuffThere are multiple trailing whitespace and blank-line-with-whitespace instances (W291/W293) in this block. Please run the formatter/ruff to clean them up.
26-27
: Optional: Identify as a well-behaved clientGitHub raw endpoints may apply tighter rate limits without a UA. Consider adding a descriptive User-Agent.
- response = requests.get( + response = requests.get( OWASP_PROJECT_LEVELS_URL, timeout=timeout, - headers={"Accept": "application/json"}, + headers={ + "Accept": "application/json", + "User-Agent": "Nest/owasp-level-fetcher (+https://github.com/OWASP/Nest)", + }, )
1-62
: Add unit tests for data shape, filtering, and type coercionPer the PR objectives, please add tests that:
- verify handling of non-list payloads (returns None),
- filter out invalid entries and normalize names via strip,
- coerce numeric levels to strings,
- propagate network/JSON errors as None.
I can scaffold tests using requests-mock and parametrized inputs if helpful. Do you want me to draft them?
backend/apps/owasp/utils/__init__.py (1)
6-6
: Add a trailing newlineStyle nit flagged by Ruff (W292).
-__all__ = ["ComplianceDetector", "ComplianceReport", "fetch_official_project_levels"] +__all__ = ["ComplianceDetector", "ComplianceReport", "fetch_official_project_levels"] +backend/apps/owasp/utils/compliance_detector.py (3)
55-58
: Use elif to reduce indentation and match linter suggestionSmall readability win.
- else: - # Project not found in official data - mark as non-compliant - if metric.is_level_compliant: - metric.is_level_compliant = False + elif metric.is_level_compliant: + # Project not found in official data - mark as non-compliant + metric.is_level_compliant = False
66-79
: Return updated count (optional) and tighten logging contextReturning the number of updates can aid orchestration/metrics; not required, but useful.
Example:
-def detect_and_update_compliance(official_levels: dict[str, str]) -> None: +def detect_and_update_compliance(official_levels: dict[str, str]) -> int: @@ - if metrics_to_update: + if metrics_to_update: ProjectHealthMetrics.objects.bulk_update( metrics_to_update, ['is_level_compliant'], batch_size=100 ) logger.info( "Updated compliance status for projects", extra={"updated_count": len(metrics_to_update)} ) - else: + return len(metrics_to_update) + else: logger.info("No compliance status changes needed") + return 0This change requires updating callers.
1-79
: Tests required for detection logic to match PR objectivesPlease add tests that:
- exercise mismatches/matches (including name stripping),
- ensure projects missing from official data are marked non-compliant,
- validate that only latest metrics are updated,
- assert bulk_update is called with the expected set and that N+1 does not occur (can be inferred via query count if you have pytest-django/pytest asserts).
I can draft pytest/pytest-django tests with model factories to cover the above. Want me to scaffold them?
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (7)
19-34
: Align with formatter/linter: switch to double quotes for CLI argsRuff flags single quotes (Q000). Use double quotes for consistency with the codebase style.
Apply:
- parser.add_argument( - '--dry-run', - action='store_true', - help='Show what would be changed without making actual updates' - ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Show what would be changed without making actual updates", + ) - parser.add_argument( - '--verbose', - action='store_true', - help='Enable verbose output for debugging' - ) + parser.add_argument( + "--verbose", + action="store_true", + help="Enable verbose output for debugging", + ) - parser.add_argument( - '--timeout', - type=int, - default=30, - help='HTTP timeout for fetching project levels (default: 30 seconds)' - ) + parser.add_argument( + "--timeout", + type=int, + default=30, + help="HTTP timeout for fetching project levels (default: 30 seconds)", + )
25-28
: Prefer Django’s built-in --verbosity over a custom --verbose flagDjango commands already support -v/--verbosity. Consider removing the custom --verbose flag or, at minimum, wiring both together to control logging.
- parser.add_argument( - "--verbose", - action="store_true", - help="Enable verbose output for debugging", - ) + # Prefer Django's built-in -v/--verbosity. If a separate flag is desired, + # keep it, but it's usually redundant. + parser.add_argument( + "--verbose", + action="store_true", + help="Enable verbose output for debugging (redundant with --verbosity)", + )And later (see next comment) gate logging on either verbose or verbosity >= 2.
36-46
: Honor Django verbosity for logging; fix quote styleUse verbosity to control logger level in addition to the custom flag. Also address Q000 (double quotes).
- start_time = time.time() - dry_run = options['dry_run'] - verbose = options['verbose'] - timeout = options['timeout'] + start_time = time.time() + dry_run = options["dry_run"] + verbose = options["verbose"] + timeout = options["timeout"] + verbosity = int(options.get("verbosity", 1)) @@ - if verbose: - logging.getLogger('apps.owasp.utils').setLevel(logging.DEBUG) + if verbose or verbosity >= 2: + logging.getLogger("apps.owasp.utils").setLevel(logging.DEBUG)
85-98
: Prefer numeric structured fields in logs over pre-formatted stringsThis makes logs easier to query/aggregate. Keep units in the key name.
- logger.info( + logger.info( "Compliance detection completed successfully", extra={ - "execution_time": f"{execution_time:.2f}s", + "execution_time_s": round(execution_time, 2), "dry_run": dry_run, "total_projects": report.total_projects_checked, "compliant_projects": len(report.compliant_projects), "non_compliant_projects": len(report.non_compliant_projects), "local_only_projects": len(report.local_only_projects), "official_only_projects": len(report.official_only_projects), - "compliance_rate": f"{report.compliance_rate:.1f}%" + # Assuming compliance_rate is in [0, 100]; adjust if it is [0, 1]. + "compliance_rate_pct": round(report.compliance_rate, 1), } )Is ComplianceReport.compliance_rate expressed as [0..100] or [0..1]? If [0..1], we must multiply by 100 before rounding/printing. I can adjust once confirmed.
115-126
: Include mismatch details when availableIf the report includes local/official level differences, surface them for faster triage; gracefully fallback to names-only when not available.
def _log_compliance_findings(self, report): """Log and display detailed compliance findings.""" # Log level mismatches for non-compliant projects if report.non_compliant_projects: - self.stderr.write(f"Found {len(report.non_compliant_projects)} non-compliant projects:") - for project_name in report.non_compliant_projects: - self.stderr.write(f" - {project_name}") - logger.warning( - "Level mismatch detected", - extra={"project": project_name} - ) + count = len(report.non_compliant_projects) + self.stderr.write(f"Found {count} non-compliant projects:") + # Support both list[str] and dict[str, dict[str,str]] forms + if isinstance(report.non_compliant_projects, dict): + items = report.non_compliant_projects.items() + else: + items = ((name, None) for name in report.non_compliant_projects) + for project_name, details in items: + if isinstance(details, dict) and {"local_level", "official_level"} <= details.keys(): + self.stderr.write( + f" - {project_name}: local={details['local_level']} != official={details['official_level']}" + ) + logger.warning( + "Level mismatch detected", + extra={ + "project": project_name, + "local_level": details["local_level"], + "official_level": details["official_level"], + }, + ) + else: + self.stderr.write(f" - {project_name}") + logger.warning( + "Level mismatch detected", + extra={"project": project_name}, + )
129-131
: Break long lines flagged by Ruff (E501)Several user-facing lines exceed 99 chars. Wrap or split to satisfy the linter.
- self.stdout.write(f"Found {len(report.local_only_projects)} projects that exist locally but not in official data:") + self.stdout.write( + f"Found {len(report.local_only_projects)} projects that exist locally " + "but not in official data:" + ) @@ - self.stdout.write(f"Found {len(report.official_only_projects)} projects in official data but not locally:") + self.stdout.write( + f"Found {len(report.official_only_projects)} projects in official data " + "but not locally:" + ) @@ - self.stdout.write(f"Found {len(report.compliant_projects)} compliant projects") + self.stdout.write( + f"Found {len(report.compliant_projects)} compliant projects" + )Also applies to: 139-141, 149-149
19-34
: Fix linter items en masse (quotes, trailing whitespace, long lines)Ruff hints (Q000, W293, E501) identify quote style, trailing whitespace in blank lines, and long lines. Recommend running ruff --fix and reflowing long strings where needed to keep CI green.
No diff provided to avoid churn; the targeted snippets above include most of the flagged lines.
Also applies to: 39-41, 45-45, 54-55, 61-61, 64-64, 69-69, 73-77, 81-83, 86-98, 118-121, 129-135, 139-145
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these settings in your CodeRabbit configuration.
📒 Files selected for processing (11)
backend/apps/owasp/Makefile
(1 hunks)backend/apps/owasp/constants.py
(1 hunks)backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
(1 hunks)backend/apps/owasp/migrations/0047_add_is_level_compliant_field.py
(1 hunks)backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
(1 hunks)backend/apps/owasp/models/project_health_metrics.py
(1 hunks)backend/apps/owasp/models/project_health_requirements.py
(1 hunks)backend/apps/owasp/utils/__init__.py
(1 hunks)backend/apps/owasp/utils/compliance_detector.py
(1 hunks)backend/apps/owasp/utils/project_level_fetcher.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
backend/apps/owasp/migrations/0047_add_is_level_compliant_field.py (1)
backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py (1)
Migration
(6-18)
backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py (1)
backend/apps/owasp/migrations/0047_add_is_level_compliant_field.py (1)
Migration
(6-18)
backend/apps/owasp/utils/compliance_detector.py (2)
backend/apps/owasp/api/internal/queries/project_health_metrics.py (1)
project_health_metrics
(25-42)backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)
backend/apps/owasp/utils/__init__.py (1)
backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-60)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-60)
🪛 Ruff (0.12.2)
backend/apps/owasp/utils/project_level_fetcher.py
5-5: typing.Dict
is deprecated, use dict
instead
(UP035)
15-15: Use dict
instead of Dict
for type annotation
Replace with dict
(UP006)
28-28: Blank line contains whitespace
Remove whitespace from blank line
(W293)
30-30: Blank line contains whitespace
Remove whitespace from blank line
(W293)
33-33: Trailing whitespace
Remove trailing whitespace
(W291)
40-40: Blank line contains whitespace
Remove whitespace from blank line
(W293)
44-44: Blank line contains whitespace
Remove whitespace from blank line
(W293)
47-47: Blank line contains whitespace
Remove whitespace from blank line
(W293)
48-50: Use a single if
statement instead of nested if
statements
(SIM102)
53-53: Consider moving this statement to an else
block
(TRY300)
57-57: Trailing whitespace
Remove trailing whitespace
(W291)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
67-67: Line too long (101 > 99)
(E501)
backend/apps/owasp/migrations/0047_add_is_level_compliant_field.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
9-9: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
14-14: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/apps/owasp/utils/compliance_detector.py
17-17: Blank line contains whitespace
Remove whitespace from blank line
(W293)
18-18: Missing blank line after last section ("Args")
Add blank line after "Args"
(D413)
22-22: Blank line contains whitespace
Remove whitespace from blank line
(W293)
24-24: Local variable active_projects
is assigned to but never used
Remove assignment to unused variable active_projects
(F841)
25-25: Blank line contains whitespace
Remove whitespace from blank line
(W293)
30-30: Blank line contains whitespace
Remove whitespace from blank line
(W293)
35-35: Blank line contains whitespace
Remove whitespace from blank line
(W293)
40-40: Blank line contains whitespace
Remove whitespace from blank line
(W293)
45-45: Blank line contains whitespace
Remove whitespace from blank line
(W293)
55-57: Use elif
instead of else
then if
, to reduce indentation
Convert to elif
(PLR5501)
60-60: Blank line contains whitespace
Remove whitespace from blank line
(W293)
65-65: Blank line contains whitespace
Remove whitespace from blank line
(W293)
69-69: Trailing whitespace
Remove trailing whitespace
(W291)
70-70: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
73-73: Blank line contains whitespace
Remove whitespace from blank line
(W293)
79-79: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/utils/__init__.py
6-6: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
20-20: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
21-21: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
22-22: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
25-25: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
26-26: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
27-27: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
30-30: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
33-33: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
39-39: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
40-40: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
41-41: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
42-42: Blank line contains whitespace
Remove whitespace from blank line
(W293)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
46-46: Blank line contains whitespace
Remove whitespace from blank line
(W293)
49-49: Blank line contains whitespace
Remove whitespace from blank line
(W293)
52-52: Blank line contains whitespace
Remove whitespace from blank line
(W293)
56-56: Blank line contains whitespace
Remove whitespace from blank line
(W293)
59-59: Abstract raise
to an inner function
(TRY301)
59-59: Avoid specifying long messages outside the exception class
(TRY003)
59-59: Exception must not use a string literal, assign to variable first
Assign to variable; remove string literal
(EM101)
60-60: Blank line contains whitespace
Remove whitespace from blank line
(W293)
61-61: Line too long (101 > 99)
(E501)
62-62: Blank line contains whitespace
Remove whitespace from blank line
(W293)
67-67: Blank line contains whitespace
Remove whitespace from blank line
(W293)
70-70: Blank line contains whitespace
Remove whitespace from blank line
(W293)
78-78: Blank line contains whitespace
Remove whitespace from blank line
(W293)
82-82: Line too long (137 > 99)
(E501)
84-84: Blank line contains whitespace
Remove whitespace from blank line
(W293)
99-99: Blank line contains whitespace
Remove whitespace from blank line
(W293)
102-102: Use explicit conversion flag
Replace with conversion flag
(RUF010)
103-103: Blank line contains whitespace
Remove whitespace from blank line
(W293)
104-104: Logging .exception(...)
should be used instead of .error(..., exc_info=True)
(G201)
112-112: Blank line contains whitespace
Remove whitespace from blank line
(W293)
113-113: Within an except
clause, raise exceptions with raise ... from err
or raise ... from None
to distinguish them from errors in exception handling
(B904)
119-119: Line too long (100 > 99)
(E501)
126-126: Blank line contains whitespace
Remove whitespace from blank line
(W293)
129-129: Line too long (127 > 99)
(E501)
136-136: Blank line contains whitespace
Remove whitespace from blank line
(W293)
139-139: Line too long (119 > 99)
(E501)
146-146: Blank line contains whitespace
Remove whitespace from blank line
(W293)
🔇 Additional comments (2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (2)
8-10
: Import root validated — 'apps.owasp' imports are consistent; no change requiredSearch found many occurrences of "from apps.owasp" and zero occurrences of "from backend.apps.owasp". The file under review (backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py) matches the repository-wide pattern (e.g., backend/settings/urls.py, backend/apps/owasp/**).
No code changes needed. Ensure your runtime/deployment config keeps backend/ on PYTHONPATH so the top-level "apps" package is importable.
82-83
: Confirm compliance_rate scale used in UI outputI couldn't find where report.compliance_rate is computed in the repo — the only occurrences are the print/log lines in the management command. If compliance_rate is a fraction in [0..1], the displayed value will be off by 100x; if it's already in [0..100], it's fine. Please confirm.
Files to check:
- backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
- Summary print and compliance output (around lines 82–83)
- logger.info extra "compliance_rate" (around line 96)
Suggested fix if compliance_rate is a fraction:
- self.stdout.write(f"Compliance rate: {report.compliance_rate:.1f}%") + self.stdout.write(f"Compliance rate: {report.compliance_rate * 100:.1f}%") ... - "compliance_rate": f"{report.compliance_rate:.1f}%" + "compliance_rate": f"{report.compliance_rate * 100:.1f}%"
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (4)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (4)
36-36
: Add tests for the management command workflow (dry-run/update/failure paths)Per issue #2039 acceptance criteria, please add pytest tests covering fetch failure, empty payload, dry-run vs update behavior, and summary/log outputs using Django’s call_command with mocks.
I can scaffold tests under backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py that patch fetch_official_project_levels and the detector and assert stdout/stderr and DB updates. Want me to generate them?
74-79
: Wrap DB update in a transaction to avoid partial state on errorsIf update_compliance_status raises mid-way, you may end up with partial updates. Use transaction.atomic().
Apply this diff:
if not dry_run: self.stdout.write("Updating compliance status in database...") - detector.update_compliance_status(report) + from django.db import transaction + with transaction.atomic(): + detector.update_compliance_status(report) self.stdout.write("Compliance status updated successfully")
69-71
: Guard against None/invalid report from detector before proceedingIf detect_compliance_issues returns None or an unexpected type, subsequent attribute access will crash. Fail fast with a CommandError.
Apply this diff:
- report = detector.detect_compliance_issues(official_levels) + report = detector.detect_compliance_issues(official_levels) + if report is None: + raise CommandError("ComplianceDetector.detect_compliance_issues returned no report")
103-116
: Use logger.exception with traceback and chain the error; also apply explicit conversion flag and perf_counter
- Prefer logger.exception(...) to automatically log the traceback.
- Chain the CommandError with "from e" to preserve context.
- Use perf_counter for timing.
- Use {e!s} instead of str(e) (RUF010).
Apply this diff:
- except Exception as e: - execution_time = time.time() - start_time - error_msg = f"Compliance detection failed after {execution_time:.2f}s: {str(e)}" - - logger.error( - "Compliance detection failed", - extra={ - "execution_time": f"{execution_time:.2f}s", - "error": str(e) - }, - exc_info=True - ) - - raise CommandError(error_msg) + except Exception as e: + execution_time = time.perf_counter() - start_time + error_msg = f"Compliance detection failed after {execution_time:.2f}s: {e!s}" + + logger.exception( + "Compliance detection failed", + extra={ + "execution_time_s": round(execution_time, 2), + "error": e.__class__.__name__, + }, + ) + raise CommandError(error_msg) from e
🧹 Nitpick comments (7)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (7)
38-38
: Use a monotonic timer for measuring durationtime.time() is wall-clock and can jump. Use time.perf_counter() for reliable elapsed time measurement.
Apply this diff:
- start_time = time.time() + start_time = time.perf_counter()
83-87
: Use perf_counter for elapsed time and split long summary lines (E501)
- Measure with perf_counter to match the start timer.
- Break the long f-string into wrapped parts to satisfy line-length rules.
Apply this diff:
- execution_time = time.time() - start_time - self.stdout.write(f"\nCompliance detection completed in {execution_time:.2f}s") - self.stdout.write(f"Summary: {len(report.compliant_projects)} compliant, {len(report.non_compliant_projects)} non-compliant") - self.stdout.write(f"Compliance rate: {report.compliance_rate:.1f}%") + execution_time = time.perf_counter() - start_time + self.stdout.write(f"\nCompliance detection completed in {execution_time:.2f}s") + self.stdout.write( + f"Summary: {len(report.compliant_projects)} compliant, " + f"{len(report.non_compliant_projects)} non-compliant" + ) + self.stdout.write(f"Compliance rate: {report.compliance_rate:.1f}%")
89-101
: Structured logging: emit numeric values for metrics instead of formatted stringsFor machine-readable logs, prefer numbers (seconds, counts, rate) over preformatted strings. This also reduces formatting overhead.
Apply this diff:
- logger.info( - "Compliance detection completed successfully", - extra={ - "execution_time": f"{execution_time:.2f}s", - "dry_run": dry_run, - "total_projects": report.total_projects_checked, - "compliant_projects": len(report.compliant_projects), - "non_compliant_projects": len(report.non_compliant_projects), - "local_only_projects": len(report.local_only_projects), - "official_only_projects": len(report.official_only_projects), - "compliance_rate": f"{report.compliance_rate:.1f}%" - } - ) + logger.info( + "Compliance detection completed successfully", + extra={ + "execution_time_s": round(execution_time, 2), + "dry_run": bool(dry_run), + "total_projects": int(report.total_projects_checked), + "compliant_projects": len(report.compliant_projects), + "non_compliant_projects": len(report.non_compliant_projects), + "local_only_projects": len(report.local_only_projects), + "official_only_projects": len(report.official_only_projects), + "compliance_rate_pct": round(report.compliance_rate, 1), + }, + )
121-125
: Break long stderr line to satisfy E501 and improve readabilityApply this diff:
- self.stderr.write(f"Found {len(report.non_compliant_projects)} non-compliant projects:") + self.stderr.write( + f"Found {len(report.non_compliant_projects)} non-compliant projects:" + )
131-138
: Break long stdout line to satisfy E501 and improve readabilityApply this diff:
- self.stdout.write(f"Found {len(report.local_only_projects)} projects that exist locally but not in official data:") + self.stdout.write( + f"Found {len(report.local_only_projects)} projects that exist locally " + f"but not in official data:" + )
141-148
: Break long stdout line to satisfy E501 and improve readabilityApply this diff:
- self.stdout.write(f"Found {len(report.official_only_projects)} projects in official data but not locally:") + self.stdout.write( + f"Found {len(report.official_only_projects)} projects in official data " + f"but not locally:" + )
20-34
: Code style: unify quotes to double quotes per Ruff Q000 and trim trailing whitespace (W293)Multiple lines use single quotes and contain trailing whitespace flagged by Ruff. Consider running ruff --fix or switching to double quotes consistently for CLI option strings to satisfy Q000 and removing blank-line whitespace.
Also applies to: 39-41, 45-45
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these settings in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-60)
🪛 Ruff (0.12.2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
20-20: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
21-21: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
22-22: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
25-25: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
26-26: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
27-27: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
30-30: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
33-33: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
39-39: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
40-40: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
41-41: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
42-42: Blank line contains whitespace
Remove whitespace from blank line
(W293)
45-45: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
46-46: Blank line contains whitespace
Remove whitespace from blank line
(W293)
49-49: Blank line contains whitespace
Remove whitespace from blank line
(W293)
52-52: Blank line contains whitespace
Remove whitespace from blank line
(W293)
60-60: Abstract raise
to an inner function
(TRY301)
65-65: Blank line contains whitespace
Remove whitespace from blank line
(W293)
70-70: Blank line contains whitespace
Remove whitespace from blank line
(W293)
73-73: Blank line contains whitespace
Remove whitespace from blank line
(W293)
81-81: Blank line contains whitespace
Remove whitespace from blank line
(W293)
85-85: Line too long (137 > 99)
(E501)
87-87: Blank line contains whitespace
Remove whitespace from blank line
(W293)
102-102: Blank line contains whitespace
Remove whitespace from blank line
(W293)
105-105: Use explicit conversion flag
Replace with conversion flag
(RUF010)
106-106: Blank line contains whitespace
Remove whitespace from blank line
(W293)
107-107: Logging .exception(...)
should be used instead of .error(..., exc_info=True)
(G201)
115-115: Blank line contains whitespace
Remove whitespace from blank line
(W293)
116-116: Within an except
clause, raise exceptions with raise ... from err
or raise ... from None
to distinguish them from errors in exception handling
(B904)
122-122: Line too long (100 > 99)
(E501)
129-129: Blank line contains whitespace
Remove whitespace from blank line
(W293)
132-132: Line too long (127 > 99)
(E501)
139-139: Blank line contains whitespace
Remove whitespace from blank line
(W293)
142-142: Line too long (119 > 99)
(E501)
149-149: Blank line contains whitespace
Remove whitespace from blank line
(W293)
🔇 Additional comments (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
57-61
: Good: empty payload treated as failure with a clear messageThis resolves the earlier edge case and the Ruff guideline about not embedding string literals directly in raise. Looks solid.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (5)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (5)
8-9
: Verify utils API: ensure ComplianceDetector exists (or switch to functional API)Past analysis indicates
apps.owasp.utils.compliance_detector
might expose a procedural API (e.g.,detect_and_update_compliance
) rather than aComplianceDetector
class/report. If the class isn’t defined, this import and subsequent usage will fail. Align the command with the actual utils API (either restore the class/report or adopt the functional API and adjust the command).Run this to confirm and surface the API in the repo:
#!/bin/bash set -euo pipefail echo "=== Search for ComplianceDetector class ===" ast-grep --pattern $'class ComplianceDetector {\n $$$\n}' || true rg -n '^class\s+ComplianceDetector\b' -S || true echo "=== Search for functional API ===" rg -n 'def\s+detect_and_update_compliance\s*\(' -S || true echo "=== Where is compliance_detector imported/re-exported? ===" rg -n '\bcompliance_detector\b' -S || true # Show the utils/compliance_detector.py if present if [ -f backend/apps/owasp/utils/compliance_detector.py ]; then echo "=== backend/apps/owasp/utils/compliance_detector.py (head) ===" sed -n '1,200p' backend/apps/owasp/utils/compliance_detector.py fi # Show utils __init__.py, in case it re-exports symbols if [ -f backend/apps/owasp/utils/__init__.py ]; then echo "=== backend/apps/owasp/utils/__init__.py ===" sed -n '1,200p' backend/apps/owasp/utils/__init__.py fi
36-36
: Add tests for the management command workflow (dry-run, failures, updates)Per issue #2039, tests should exercise: fetch failure (None/empty), dry-run vs update modes, detailed logs, and summary. I can scaffold pytest tests using Django’s
call_command
with mocks.Do you want me to generate them under backend/tests/apps/owasp/management/commands/?
66-73
: Guard against None/invalid report from detectorDefensively handle a None or invalid report before using it. This also improves error messaging early.
detector = ComplianceDetector() report = detector.detect_compliance_issues(official_levels) + if report is None: + msg = "ComplianceDetector.detect_compliance_issues returned no report" + self.stderr.write(msg) + raise CommandError(msg)
3-11
: Import transaction at module scope (pairs with the fix above)Add the transaction import at the top and keep imports consistent.
import logging import time from django.core.management.base import BaseCommand, CommandError +from django.db import transaction from apps.owasp.utils.compliance_detector import ComplianceDetector from apps.owasp.utils.project_level_fetcher import fetch_official_project_levels
74-81
: Fix fatal SyntaxError: unindented import breaks the try-block
from django.db import transaction
at Line 74 is at module indentation while you are insidehandle()
’stry:
block. This creates a SyntaxError (“Expected except or finally after try”). Move the import to module scope and remove the in-function import.Apply this diff to remove the stray import here:
- from django.db import transaction
Then add the import at the module top (see my separate comment for Lines 3–11).
🧹 Nitpick comments (4)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (4)
88-90
: Fix E501: split long summary linesWrap the long f-string to satisfy line-length limits and improve readability.
- self.stdout.write(f"Summary: {len(report.compliant_projects)} compliant, {len(report.non_compliant_projects)} non-compliant") + self.stdout.write( + f"Summary: {len(report.compliant_projects)} compliant, " + f"{len(report.non_compliant_projects)} non-compliant" + ) - self.stdout.write(f"Compliance rate: {report.compliance_rate:.1f}%") + self.stdout.write( + f"Compliance rate: {report.compliance_rate:.1f}%" + )
91-104
: Prefer numeric values in structured logsAvoid embedding units in structured log fields so they remain machine-friendly; keep units in message if needed.
- "execution_time": f"{execution_time:.2f}s", + "execution_time_s": round(execution_time, 2), @@ - "compliance_rate": f"{report.compliance_rate:.1f}%" + "compliance_rate_pct": round(report.compliance_rate, 1),
44-46
: Leverage Django’s built-inverbosity
option instead of custom--verbose
Django already provides
--verbosity
(0–3). Consider removing--verbose
and usingoptions["verbosity"] >= 2
to set debug logging.Example:
verbosity = int(options.get("verbosity", 1)) if verbosity >= 2: logging.getLogger('apps.owasp.utils').setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
121-156
: Line length and minor logging improvements in _log_compliance_findingsWrap long writes to satisfy E501 and keep logs tidy.
- if report.non_compliant_projects: - self.stderr.write(f"Found {len(report.non_compliant_projects)} non-compliant projects:") + if report.non_compliant_projects: + self.stderr.write( + f"Found {len(report.non_compliant_projects)} non-compliant projects:" + ) @@ - if report.local_only_projects: - self.stdout.write(f"Found {len(report.local_only_projects)} projects that exist locally but not in official data:") + if report.local_only_projects: + self.stdout.write( + f"Found {len(report.local_only_projects)} projects that exist locally " + f"but not in official data:" + ) @@ - if report.official_only_projects: - self.stdout.write(f"Found {len(report.official_only_projects)} projects in official data but not locally:") + if report.official_only_projects: + self.stdout.write( + f"Found {len(report.official_only_projects)} projects in official data " + f"but not locally:" + )Optional: add a type hint if
ComplianceReport
exists in utils:def _log_compliance_findings(self, report: "ComplianceReport") -> None: ...
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these settings in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-60)
🪛 Ruff (0.12.2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
42-42: Blank line contains whitespace
(W293)
46-46: Blank line contains whitespace
(W293)
49-49: Blank line contains whitespace
(W293)
52-52: Blank line contains whitespace
(W293)
65-65: Blank line contains whitespace
(W293)
70-70: Blank line contains whitespace
(W293)
73-73: Blank line contains whitespace
(W293)
74-74: SyntaxError: Expected except
or finally
after try
block
77-77: SyntaxError: Unexpected indentation
84-84: Blank line contains whitespace
(W293)
88-88: Line too long (137 > 99)
(E501)
90-90: Blank line contains whitespace
(W293)
105-105: Blank line contains whitespace
(W293)
106-106: SyntaxError: unindent does not match any outer indentation level
106-106: SyntaxError: Expected a statement
106-106: SyntaxError: Expected a statement
106-107: SyntaxError: Expected an expression
107-107: SyntaxError: Unexpected indentation
109-109: Blank line contains whitespace
(W293)
118-118: Blank line contains whitespace
(W293)
121-121: SyntaxError: unindent does not match any outer indentation level
125-125: Line too long (100 > 99)
(E501)
132-132: Blank line contains whitespace
(W293)
135-135: Line too long (127 > 99)
(E501)
142-142: Blank line contains whitespace
(W293)
145-145: Line too long (119 > 99)
(E501)
152-152: Blank line contains whitespace
(W293)
🔇 Additional comments (2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (2)
57-61
: Good: empty or None payload treated as failure with clear messagechecking
is None or not official_levels
and assigning tomsg
before raising fixes both functional and lint concerns.
79-83
: DB update wrapped in a transaction — goodUsing
transaction.atomic()
makes the update all-or-nothing and prevents partial state on failures.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
…hub.com/divyanshu-vr/Nest into feature/ProjectLevelComplianceDetection
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (2)
backend/apps/owasp/utils/__init__.py (1)
3-6
: Re-exports aligned with actual API — good fixSwitching to export detect_and_update_compliance and fetch_official_project_levels resolves the earlier ImportError risk.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
68-71
: Dry-run is not honored — command still updates the DBThe command calls detect_and_update_compliance unconditionally, so a dry-run will persist changes. Gate the update or pass a dry_run flag to the utility.
- # Steps 2-4: Detect and update in one procedural call - self.stdout.write("Detecting and updating compliance issues...") - detect_and_update_compliance(official_levels) + # Steps 2-4: Detect and update (or preview in dry-run) + if dry_run: + self.stdout.write("Detecting compliance issues (dry-run)...") + updated_count = detect_and_update_compliance( + official_levels, + dry_run=True, + ) + self.stdout.write(f"DRY RUN: would update {updated_count} project(s)") + else: + self.stdout.write("Detecting and updating compliance issues...") + updated_count = detect_and_update_compliance( + official_levels, + dry_run=False, + ) + self.stdout.write(f"Compliance status updated for {updated_count} project(s)")Note: This requires detect_and_update_compliance to accept dry_run and return the update count (see companion change suggested in compliance_detector.py).
🧹 Nitpick comments (8)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
67-72
: Reduce log noise and fix the long line (E501) by gating and refactoring messageAvoid logging when the penalty is 0% and reflow the message to satisfy the linter.
- self.stdout.write( - self.style.WARNING( - f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " - f"(penalty: {penalty_amount:.2f}, final score: {score:.2f})" - ) - ) + if penalty_amount > 0.0: + msg = ( + f"Applied {penalty_percentage}% compliance penalty to " + f"{metric.project.name} (penalty: {penalty_amount:.2f}, " + f"final score: {score:.2f})" + ) + self.stdout.write(self.style.WARNING(msg))backend/apps/owasp/utils/project_level_fetcher.py (1)
3-5
: Tidy imports, modernize types, and set a polite User-Agent
- Remove unused import (json).
- Prefer built-in generics: dict[str, str] over typing.Dict.
- Add a User-Agent header to be a good API citizen when calling GitHub endpoints.
- Clean up minor whitespace flagged by Ruff.
- import json import logging -from typing import Dict +from typing import Dict # TODO: Remove if unused elsewhere in the module @@ -def fetch_official_project_levels(timeout: int = 30) -> Dict[str, str] | None: +def fetch_official_project_levels(timeout: int = 30) -> dict[str, str] | None: @@ - response = requests.get( - OWASP_PROJECT_LEVELS_URL, - timeout=timeout, - headers={"Accept": "application/json"}, - ) + response = requests.get( + OWASP_PROJECT_LEVELS_URL, + timeout=timeout, + headers={ + "Accept": "application/json", + "User-Agent": "OWASP-Nest/ComplianceFetcher", + }, + ) @@ - logger.exception( - "Invalid project levels data format", + logger.exception( + "Invalid project levels data format", extra={"expected": "list", "got": type(data).__name__} )Note: You can also drop the leftover
from typing import Dict
entirely if nothing else uses it.Also applies to: 15-15, 26-33, 35-35, 42-42
backend/apps/owasp/utils/__init__.py (1)
6-6
: Add trailing newline (W292)Ensure the file ends with a single newline to satisfy linters.
-__all__ = ["detect_and_update_compliance", "fetch_official_project_levels"] +__all__ = ["detect_and_update_compliance", "fetch_official_project_levels"] +backend/apps/owasp/utils/compliance_detector.py (2)
9-9
: Remove unused import
Project
is not used in this module.-from apps.owasp.models.project import Project
56-60
: Preferelif
to reduce nestingUse
elif
for clarity.- else: - # Project not found in official data - mark as non-compliant - if metric.is_level_compliant: + elif metric.is_level_compliant: + # Project not found in official data - mark as non-compliant metric.is_level_compliant = False metrics_to_update.append(metric)backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (3)
7-7
: Remove unused import (F401)
transaction
is not used in this command.-from django.db import transaction
73-84
: Compute counts via DB to avoid materializing the querysetAvoid iterating the queryset in Python; let the DB count rows.
- latest_metrics = ProjectHealthMetrics.get_latest_health_metrics() - total = len(latest_metrics) - compliant = sum(1 for m in latest_metrics if m.is_level_compliant) - non_compliant = total - compliant + latest_metrics = ProjectHealthMetrics.get_latest_health_metrics() + total = latest_metrics.count() + compliant = latest_metrics.filter(is_level_compliant=True).count() + non_compliant = total - compliant
111-111
: Add trailing newline (W292)Ensure the file ends with a single newline.
- raise CommandError(error_msg) from e - + raise CommandError(error_msg) from e +
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (6)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
(1 hunks)backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
(1 hunks)backend/apps/owasp/utils/__init__.py
(1 hunks)backend/apps/owasp/utils/compliance_detector.py
(1 hunks)backend/apps/owasp/utils/project_level_fetcher.py
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-06-29T00:41:32.198Z
Learnt from: ahmedxgouda
PR: OWASP/Nest#1676
File: backend/apps/owasp/graphql/filters/project_health_metrics.py:17-22
Timestamp: 2025-06-29T00:41:32.198Z
Learning: In the OWASP Nest codebase, when implementing GraphQL filters that convert string values to enums (like ProjectLevel), do not catch ValueError exceptions for invalid values. Let the errors propagate to provide proper error responses to GraphQL clients rather than silently ignoring invalid input.
Applied to files:
backend/apps/owasp/utils/project_level_fetcher.py
📚 Learning: 2025-08-04T15:55:08.017Z
Learnt from: ahmedxgouda
PR: OWASP/Nest#1967
File: backend/apps/owasp/api/internal/filters/project_health_metrics.py:24-27
Timestamp: 2025-08-04T15:55:08.017Z
Learning: In the OWASP Nest project, the `is_active` filter in `ProjectHealthMetricsFilter` is intentionally designed as a workaround to eliminate inactive projects from the dashboard. It only filters FOR active projects when `value=True`, and returns an empty Q() when `value=False` to avoid showing inactive projects. This is not meant to be a general boolean filter but a specific solution to exclude inactive projects from the project health metrics dashboard.
Applied to files:
backend/apps/owasp/utils/compliance_detector.py
🧬 Code Graph Analysis (3)
backend/apps/owasp/utils/__init__.py (2)
backend/apps/owasp/utils/compliance_detector.py (1)
detect_and_update_compliance
(15-80)backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-62)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (4)
backend/apps/owasp/utils/compliance_detector.py (1)
detect_and_update_compliance
(15-80)backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(15-62)backend/apps/owasp/api/internal/queries/project_health_metrics.py (1)
project_health_metrics
(25-42)backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)
backend/apps/owasp/utils/compliance_detector.py (2)
backend/apps/owasp/api/internal/queries/project_health_metrics.py (1)
project_health_metrics
(25-42)backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)
🪛 Ruff (0.12.2)
backend/apps/owasp/utils/project_level_fetcher.py
3-3: json
imported but unused
Remove unused import: json
(F401)
5-5: typing.Dict
is deprecated, use dict
instead
(UP035)
15-15: Use dict
instead of Dict
for type annotation
Replace with dict
(UP006)
35-35: Trailing whitespace
Remove trailing whitespace
(W291)
42-42: Blank line contains whitespace
Remove whitespace from blank line
(W293)
55-55: Consider moving this statement to an else
block
(TRY300)
backend/apps/owasp/utils/__init__.py
6-6: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
7-7: django.db.transaction
imported but unused
Remove unused import: django.db.transaction
(F401)
22-22: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
23-23: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
24-24: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
27-27: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
28-28: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
29-29: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
32-32: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
35-35: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
41-41: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
42-42: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
43-43: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
44-44: Blank line contains whitespace
Remove whitespace from blank line
(W293)
47-47: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
48-48: Blank line contains whitespace
Remove whitespace from blank line
(W293)
51-51: Blank line contains whitespace
Remove whitespace from blank line
(W293)
54-54: Blank line contains whitespace
Remove whitespace from blank line
(W293)
62-62: Abstract raise
to an inner function
(TRY301)
67-67: Blank line contains whitespace
Remove whitespace from blank line
(W293)
71-71: Blank line contains whitespace
Remove whitespace from blank line
(W293)
79-79: Blank line contains whitespace
Remove whitespace from blank line
(W293)
85-85: Blank line contains whitespace
Remove whitespace from blank line
(W293)
98-98: Blank line contains whitespace
Remove whitespace from blank line
(W293)
111-111: Blank line contains whitespace
Remove whitespace from blank line
(W293)
111-111: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
69-69: Line too long (101 > 99)
(E501)
backend/apps/owasp/utils/compliance_detector.py
9-9: apps.owasp.models.project.Project
imported but unused
Remove unused import: apps.owasp.models.project.Project
(F401)
15-15: Unused function argument: official_levels
(ARG001)
17-17: Blank line contains whitespace
Remove whitespace from blank line
(W293)
18-18: Missing blank line after last section ("Args")
Add blank line after "Args"
(D413)
22-22: Blank line contains whitespace
Remove whitespace from blank line
(W293)
29-29: Blank line contains whitespace
Remove whitespace from blank line
(W293)
34-34: Blank line contains whitespace
Remove whitespace from blank line
(W293)
38-38: Undefined name normalized_official_levels
(F821)
39-39: Undefined name normalized_official_levels
(F821)
46-46: Blank line contains whitespace
Remove whitespace from blank line
(W293)
56-58: Use elif
instead of else
then if
, to reduce indentation
Convert to elif
(PLR5501)
61-61: Blank line contains whitespace
Remove whitespace from blank line
(W293)
66-66: Blank line contains whitespace
Remove whitespace from blank line
(W293)
70-70: Trailing whitespace
Remove trailing whitespace
(W291)
71-71: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
74-74: Blank line contains whitespace
Remove whitespace from blank line
(W293)
80-80: No newline at end of file
Add trailing newline
(W292)
🔇 Additional comments (3)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (2)
60-66
: Penalty logic and clamping look correctClamping [0, 100] and flooring the score at 0.0 avoids bad configs from causing negative scores or inflating penalties. Good defensive change.
73-74
: Final score bound check is appropriateBounding the final score to [0, 100] is consistent with model validators and prevents drift. LGTM.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
99-111
: Good failure handling and structured loggingCatching, timing with perf_counter, using logger.exception, and chaining the CommandError are all solid practices here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (8)
cron/production (1)
5-5
: Nit: log file naming consistency with existing cron linesOther lines (3–4) don’t use the “.log” suffix. For consistency with existing entries, consider dropping “.log” here or standardizing all log files in a separate change. Ensure logrotate picks up the chosen filename.
-25 17 * * * cd /home/production; make owasp-detect-project-level-compliance > /var/log/nest/production/detect-project-level-compliance.log 2>&1 +25 17 * * * cd /home/production; make owasp-detect-project-level-compliance > /var/log/nest/production/detect-project-level-compliance 2>&1backend/apps/owasp/utils/project_level_fetcher.py (1)
45-50
: Normalize numeric levels to avoid “2.0” vs “2” false mismatchesIf upstream provides float-like numbers (e.g., 2.0), we’ll store "2.0" while local may be "2". Canonicalize floats with .is_integer() to stringified ints to reduce false negatives.
- ): - project_levels[project_name.strip()] = str(level) + ): + # Canonicalize numeric levels: 2.0 -> "2", keep other values as-is + if isinstance(level, float) and level.is_integer(): + level_str = str(int(level)) + else: + level_str = str(level) + project_levels[project_name.strip()] = level_strbackend/apps/owasp/utils/compliance_detector.py (2)
14-17
: Make dry_run keyword-only to prevent accidental positional misuseMinor API hardening: enforce keyword-only for dry_run. This prevents subtle call-site bugs when adding parameters later.
-def detect_and_update_compliance( - official_levels: dict[str, str], - dry_run: bool = False, -) -> int: +def detect_and_update_compliance( + official_levels: dict[str, str], + *, + dry_run: bool = False, +) -> int:
91-91
: Ensure trailing newline at EOFSome linters flag missing EOF newline (W292). Add a newline to keep linters happy.
- return len(metrics_to_update) + return len(metrics_to_update) +backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (4)
64-69
: Compute summary via DB aggregation to avoid loading all rowslen(queryset) and iterating to sum pulls all rows into memory. Use aggregation with filtered Count for scalability.
- latest_metrics = ProjectHealthMetrics.get_latest_health_metrics() - total = len(latest_metrics) - compliant = sum(1 for m in latest_metrics if m.is_level_compliant) - non_compliant = total - compliant - compliance_rate = (compliant / total * 100) if total else 0.0 + from django.db.models import Count, Q + stats = ProjectHealthMetrics.get_latest_health_metrics().aggregate( + total=Count("id"), + compliant=Count("id", filter=Q(is_level_compliant=True)), + ) + total = stats.get("total") or 0 + compliant = stats.get("compliant") or 0 + non_compliant = total - compliant + compliance_rate = (compliant / total * 100.0) if total else 0.0Add the import at the top of the file:
from django.db.models import Count, Q
79-88
: Prefer numeric fields in structured logs; include updated_countMake logs easier to query by storing numerics, not formatted strings. Also include updated_count.
logger.info( "Compliance detection completed successfully", extra={ - "execution_time": f"{execution_time:.2f}s", + "execution_time_s": round(execution_time, 2), "dry_run": dry_run, "total_projects": total, "compliant_projects": compliant, "non_compliant_projects": non_compliant, - "compliance_rate": f"{compliance_rate:.1f}%", + "compliance_rate_pct": round(compliance_rate, 1), + "updated_count": updated_count, }, )
35-60
: End-to-end tests for command behavior (dry-run, empty payload, logging, counts)Per #2039 acceptance criteria, please add pytest tests for:
- fetch failure or empty payload → CommandError and stderr message
- dry-run mode → no DB writes; “DRY RUN” messaging
- normal run → DB writes occur; final summary reflects counts
- verbose flag → utils loggers switch to DEBUG (can assert logger level or emitted debug logs)
I can scaffold tests using call_command and unittest.mock for the fetcher and detector.
Would you like me to generate a test module under backend/tests/apps/owasp/management/commands/ covering these cases?
101-101
: Ensure trailing newline at EOFKeep linters happy (W292).
- raise CommandError(error_msg) from e + raise CommandError(error_msg) from e +
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
(1 hunks)backend/apps/owasp/utils/compliance_detector.py
(1 hunks)backend/apps/owasp/utils/project_level_fetcher.py
(1 hunks)cron/production
(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/apps/owasp/migrations/0048_add_compliance_penalty_weight.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-08-04T15:55:08.017Z
Learnt from: ahmedxgouda
PR: OWASP/Nest#1967
File: backend/apps/owasp/api/internal/filters/project_health_metrics.py:24-27
Timestamp: 2025-08-04T15:55:08.017Z
Learning: In the OWASP Nest project, the `is_active` filter in `ProjectHealthMetricsFilter` is intentionally designed as a workaround to eliminate inactive projects from the dashboard. It only filters FOR active projects when `value=True`, and returns an empty Q() when `value=False` to avoid showing inactive projects. This is not meant to be a general boolean filter but a specific solution to exclude inactive projects from the project health metrics dashboard.
Applied to files:
backend/apps/owasp/utils/compliance_detector.py
📚 Learning: 2025-06-29T00:41:32.198Z
Learnt from: ahmedxgouda
PR: OWASP/Nest#1676
File: backend/apps/owasp/graphql/filters/project_health_metrics.py:17-22
Timestamp: 2025-06-29T00:41:32.198Z
Learning: In the OWASP Nest codebase, when implementing GraphQL filters that convert string values to enums (like ProjectLevel), do not catch ValueError exceptions for invalid values. Let the errors propagate to provide proper error responses to GraphQL clients rather than silently ignoring invalid input.
Applied to files:
backend/apps/owasp/utils/project_level_fetcher.py
🧬 Code Graph Analysis (2)
backend/apps/owasp/utils/compliance_detector.py (2)
backend/apps/owasp/api/internal/queries/project_health_metrics.py (1)
project_health_metrics
(25-42)backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (4)
backend/apps/owasp/api/internal/queries/project_health_metrics.py (1)
project_health_metrics
(25-42)backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)backend/apps/owasp/utils/compliance_detector.py (1)
detect_and_update_compliance
(14-91)backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(13-59)
🪛 Ruff (0.12.2)
backend/apps/owasp/utils/compliance_detector.py
15-15: Trailing whitespace
Remove trailing whitespace
(W291)
16-16: Boolean-typed positional argument in function definition
(FBT001)
16-16: Boolean default positional argument in function definition
(FBT002)
19-19: Blank line contains whitespace
Remove whitespace from blank line
(W293)
23-23: Blank line contains whitespace
Remove whitespace from blank line
(W293)
24-24: Missing blank line after last section ("Returns")
Add blank line after "Returns"
(D413)
30-30: Trailing whitespace
Remove trailing whitespace
(W291)
43-43: Blank line contains whitespace
Remove whitespace from blank line
(W293)
80-80: Trailing whitespace
Remove trailing whitespace
(W291)
81-81: Trailing whitespace
Remove trailing whitespace
(W291)
90-90: Blank line contains whitespace
Remove whitespace from blank line
(W293)
91-91: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
55-55: Abstract raise
to an inner function
(TRY301)
101-101: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/utils/project_level_fetcher.py
52-52: Consider moving this statement to an else
block
(TRY300)
🔇 Additional comments (2)
backend/apps/owasp/utils/project_level_fetcher.py (1)
13-59
: Fetcher looks solid and robustGood use of Accept header, response.json(), shape validation, and defensive parsing. Logging on failures is appropriate.
backend/apps/owasp/utils/compliance_detector.py (1)
28-69
: Good normalization, dry-run behavior, and bulk_update usage
- Keys and levels normalization avoids whitespace/case mismatches.
- select_related("project") prevents N+1.
- dry_run is honored; updates are batched.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (2)
cron/production (1)
5-5
: LGTM: detection runs before scoring nowScheduling compliance detection at 17:21 ensures the score update at 17:22 uses fresh compliance flags. This aligns with earlier feedback.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
15-17
: Tests still needed per acceptance criteriaDraft noted; when you add tests, cover fetch failures/empty payload, dry-run vs update behavior, summary counts, and logging.
I can scaffold pytest tests using call_command and mocks for fetch_official_project_levels and detect_and_update_compliance. Want me to generate them?
🧹 Nitpick comments (5)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (5)
66-70
: Avoid materializing the entire QuerySet; use DB-side countsCurrent approach iterates the full result set to compute counts. Use .count() and a filtered count to reduce memory and latency on large datasets.
- latest_metrics = ProjectHealthMetrics.get_latest_health_metrics() - total = len(latest_metrics) - compliant = sum(1 for m in latest_metrics if m.is_level_compliant) - non_compliant = total - compliant + latest_metrics = ProjectHealthMetrics.get_latest_health_metrics() + total = latest_metrics.count() + compliant = latest_metrics.filter(is_level_compliant=True).count() + non_compliant = total - compliant
42-44
: Verbose mode should enable this command’s logger tooYou’re setting DEBUG only for apps.owasp.utils. Also set this module’s logger (and optionally apps.owasp) so verbose mode covers all relevant logs.
if verbose: - logging.getLogger("apps.owasp.utils").setLevel(logging.DEBUG) + logging.getLogger("apps.owasp.utils").setLevel(logging.DEBUG) + logging.getLogger("apps.owasp").setLevel(logging.DEBUG) + logger.setLevel(logging.DEBUG)
81-91
: Standardize telemetry key for execution timeSuccess log uses execution_time while error log uses execution_time_s. Unify to a single key for easier aggregation.
logger.info( "Compliance detection completed successfully", extra={ - "execution_time": f"{execution_time:.2f}s", + "execution_time_s": round(execution_time, 2), "dry_run": dry_run, "total_projects": total, "compliant_projects": compliant, "non_compliant_projects": non_compliant, "compliance_rate": f"{compliance_rate:.1f}%", }, )
92-103
: Avoid double-wrapping CommandErrorFailures raised intentionally via fail(...) become CommandError, which are then caught and re-wrapped here. Let CommandError bubble unchanged; keep structured logging for unexpected exceptions.
- except Exception as e: + except CommandError: + # Let explicit command failures propagate without double-wrapping + raise + except Exception as e: execution_time = time.perf_counter() - start_time error_msg = f"Compliance detection failed after {execution_time:.2f}s: {e!s}" logger.exception( "Compliance detection failed", extra={ "execution_time_s": round(execution_time, 2), "error": e.__class__.__name__, }, ) raise CommandError(error_msg) from e
103-103
: Add trailing newline (W292)File is missing a trailing newline; add one to satisfy linters.
- raise CommandError(error_msg) from e + raise CommandError(error_msg) from e +
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)cron/production
(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (3)
backend/apps/owasp/models/project_health_metrics.py (2)
ProjectHealthMetrics
(16-240)get_latest_health_metrics
(165-179)backend/apps/owasp/utils/compliance_detector.py (1)
detect_and_update_compliance
(14-91)backend/apps/owasp/utils/project_level_fetcher.py (1)
fetch_official_project_levels
(13-59)
🪛 Ruff (0.12.2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
54-54: Abstract raise
to an inner function
(TRY301)
103-103: No newline at end of file
Add trailing newline
(W292)
🔇 Additional comments (1)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
56-61
: Good: treat empty payload as failure and report count on successThe None/empty check prevents misleading “success” with zero records, and the success message is concise.
@arkid15r Do these look good? If yes, I’ll go ahead and start creating the necessary test files. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has to be reshaped a lot:
- add
project_level_official
field forowasp::Project
model: the project is not compliant when its project_level != project_level_official - update project sync process with populating the new field
- move the new logic to existing project health/score related commands
Also you need to start following the contribution guidelines and run code quality checks if you expect your PRs to be reviewed.
cron/production
Outdated
@@ -2,3 +2,4 @@ | |||
17 05 * * * cd /home/production; make sync-data > /var/log/nest/production/sync-data.log 2>&1 | |||
17 17 * * * cd /home/production; make owasp-update-project-health-requirements && make owasp-update-project-health-metrics > /var/log/nest/production/update-project-health-metrics 2>&1 | |||
22 17 * * * cd /home/production; make owasp-update-project-health-scores > /var/log/nest/production/update-project-health-scores 2>&1 | |||
21 17 * * * cd /home/production; make owasp-detect-project-level-compliance > /var/log/nest/production/detect-project-level-compliance.log 2>&1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not needed -- we have enough cron jobs -- they can cover project level compliance too. The project health metrics (and then project health scores) jobs should be ones that taking care of this too.
backend/apps/owasp/constants.py
Outdated
@@ -1,3 +1,4 @@ | |||
"""OWASP app constants.""" | |||
|
|||
OWASP_ORGANIZATION_NAME = "OWASP" | |||
OWASP_PROJECT_LEVELS_URL = "https://raw.githubusercontent.com/OWASP/owasp.github.io/main/_data/project_levels.json" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used for only one command, you can keep it right in there.
@@ -60,3 +60,7 @@ owasp-update-events: | |||
owasp-update-sponsors: | |||
@echo "Getting OWASP sponsors data" | |||
@CMD="python manage.py owasp_update_sponsors" $(MAKE) exec-backend-command | |||
|
|||
owasp-detect-project-level-compliance: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I recommend splitting it into 2 parts:
- syncing true project levels during the project sync
- updating health metrics/scores with the new level compliance logic
…hub.com/divyanshu-vr/Nest into feature/ProjectLevelComplianceDetection
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (1)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
60-75
: Thanks for implementing penalty clamping and final score boundsClamping the penalty to [0, 100] and capping the final score 0–100 addresses earlier feedback and guards against bad data. 👍
🧹 Nitpick comments (42)
backend/tests/apps/owasp/models/project_test.py (1)
135-155
: Solid coverage for is_level_compliant; consider a couple of edge casesThe parametrized matrix and default case look good. Two optional improvements:
- Add a case where raw strings are used instead of TextChoices to ensure equality still behaves as expected (Django stores string values on assignment).
- Add a case where local level is set to OTHER and official is non-OTHER to assert False explicitly (complements existing tests).
Would you like me to add those tests?
backend/apps/owasp/migrations/0049_remove_projecthealthrequirements_owasp_compliance_penalty_weight_0_100_and_more.py (1)
13-19
: Style: consider ignoring Ruff Q000 in migrations or switch to double quotesRuff flags single quotes here. Since migrations are auto-generated, either:
- Add a per-file ignore for Q000 in this migration, or
- Switch to double quotes to satisfy the linter.
Apply this diff if you prefer quotes change:
- ('owasp', '0048_add_compliance_penalty_weight'), + ("owasp", "0048_add_compliance_penalty_weight"), @@ - model_name='projecthealthrequirements', - name='compliance_penalty_weight', - field=models.FloatField(default=10.0, help_text='Percentage penalty applied to non-compliant projects (0-100)', validators=[django.core.validators.MinValueValidator(0.0), django.core.validators.MaxValueValidator(100.0)], verbose_name='Compliance penalty weight (%)'), + model_name="projecthealthrequirements", + name="compliance_penalty_weight", + field=models.FloatField( + default=10.0, + help_text="Percentage penalty applied to non-compliant projects (0-100)", + validators=[ + django.core.validators.MinValueValidator(0.0), + django.core.validators.MaxValueValidator(100.0), + ], + verbose_name="Compliance penalty weight (%)", + ),backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
60-75
: Avoid noisy warnings when penalty is 0% and wrap long lines (fix E501)Currently a warning is emitted even when the clamped penalty is 0%, and the f-string line length exceeds 99 chars. Recommend logging only when a non-zero penalty applies and wrapping the message.
Apply this diff in this block:
- if not metric.project.is_level_compliant: + if not metric.project.is_level_compliant: penalty_percentage = float(getattr(requirements, "compliance_penalty_weight", 0.0)) # Clamp to [0, 100] penalty_percentage = max(0.0, min(100.0, penalty_percentage)) penalty_amount = score * (penalty_percentage / 100.0) score = max(0.0, score - penalty_amount) - self.stdout.write( - self.style.WARNING( - f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " - f"(penalty: {penalty_amount:.2f}, final score: {score:.2f}) " - f"[Local: {metric.project.level}, Official: {metric.project.project_level_official}]" - ) - ) + if penalty_percentage > 0.0: + msg = ( + f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " + f"(penalty: {penalty_amount:.2f}, final score: {score:.2f}) " + f"[Local: {metric.project.level}, Official: {metric.project.project_level_official}]" + ) + self.stdout.write(self.style.WARNING(msg))backend/apps/owasp/models/project.py (2)
63-69
: New official level field is appropriate; consider indexing if queried oftenAdding project_level_official with choices/default is aligned with the workflow. If you’ll frequently query by official level (e.g., reports), consider adding a DB index.
Example migration snippet:
migrations.AddIndex( model_name="project", index=models.Index(fields=["project_level_official"], name="project_official_level_idx"), )
161-165
: Property-based compliance is simple and effective; confirm acceptance criteria on persistenceThe derived property is fine for checks/penalties. The linked issue mentioned “introduce a new boolean field to mark projects as non-compliant.” If a persistent flag is still required for analytics or filtering, confirm whether:
- A stored boolean exists elsewhere (e.g., on ProjectHealthMetrics), or
- The property suffices and the issue can be updated accordingly.
If you need a stored flag on Project with automatic maintenance, I can draft the model/migration and signal logic.
backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py (4)
17-23
: Fixture doesn’t need yield; remove unused Command instanceThe autouse fixture doesn’t tear down and the instantiated Command isn’t used.
Apply this diff:
- @pytest.fixture(autouse=True) - def _setup(self): + @pytest.fixture(autouse=True) + def _setup(self): """Set up test environment.""" self.stdout = StringIO() - self.command = Command() - yield + returnAnd drop the unused import:
-from apps.owasp.management.commands.owasp_detect_project_level_compliance import Command
60-88
: Mixed scenario coverage is clear; tiny readability tweakOptional: use keyword for the boolean to avoid FBT003 lint in function calls.
Example:
- self.create_mock_project("OWASP ZAP", "flagship", "flagship", True), + self.create_mock_project("OWASP ZAP", "flagship", "flagship", is_compliant=True),
126-147
: Default official levels info path: watch for special characters in CIYou assert on a line containing “ℹ”. Depending on CI locale, that glyph can be stripped/changed. If you hit flaky output, consider asserting on the tail of the message without the symbol.
Example:
assert "projects have default official levels" in output
148-175
: Parametric rate calc test is strong; minor lint fixes and EOF newline
- A couple of long lines >99 chars and trailing whitespace are flagged by Ruff.
- Add a trailing newline at EOF to silence W292.
Run “ruff --fix” on this file, or I can push a formatted patch if you prefer.
backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py (7)
85-152
: Good end-to-end penalty scenario; tighten stdout assertions and avoid over-coupling to formattingThe scenario is solid. Two nits:
- Asserting substrings like "penalty:" and "final score:" risks brittleness if formatting changes. Prefer explicit, separate assertions for each expected token or a regex that tolerates spacing.
- Since Project.is_level_compliant is a property in the model, setting a boolean attribute on a MagicMock works here because mock_metric.project is itself a MagicMock. If we later switch to spec-enforced mocks, use PropertyMock to avoid surprises.
Would you like me to switch these checks to precise regex assertions and wire PropertyMock for is_level_compliant now?
141-142
: Remove unused variable to satisfy Ruff (F841)expected_score is assigned but not used.
- expected_score = 72.0 - +
150-150
: Split combined assertion for clarity (PT018)Break this into two asserts for better failure messages.
- assert "penalty:" in output and "final score:" in output + assert "penalty:" in output + assert "final score:" in output
153-210
: Compliant path looks good; consider asserting that no penalty line is printedThe ≥ comparison is fine, but also assert absence of the "Applied X% compliance penalty" line for stronger signal.
output = self.stdout.getvalue() - assert "compliance penalty" not in output + assert "compliance penalty" not in output + assert "Applied " not in output
211-253
: Zero-penalty case: assertion intent is good; keep only the base-score check and explicit log checkTwo small nits:
- expected_base_score is unused.
- Keep the explicit "Applied 0.0% compliance penalty" check as-is; it's valuable.
- expected_base_score = 90.0 # All fields meet requirements - # With zero penalty, score should be the base score + # With zero penalty, score should remain at the base score assert mock_metric.score >= 90.0 # Should be base score or higher
291-338
: Nice clamping coverage; also assert that values are numerically clamped in score, not just logsYou already assert the log message. Consider also validating the relationship between prior (base) score and final score for each clamp, e.g., 50% cuts the base in half.
I can add a small helper to compute a predictable base score in-test and assert the exact clamped result for (-10→0, 150→100, 50→50).
109-330
: Trailing whitespace and style nits (W293/E501/PT018/F841)There are several trailing whitespace lines (W293), a couple of long lines (E501), one combined assertion (PT018), and a few unused variables (F841). Running ruff --fix and black should clean these up quickly.
I can push a formatting-only commit that fixes W293/E501/PT018/F841 across this file.
backend/tests/apps/owasp/management/commands/owasp_update_project_health_metrics_test.py (6)
3-3
: Remove unused importjson
(F401)Not used anywhere in this module.
-from unittest.mock import MagicMock, patch -import json +from unittest.mock import MagicMock, patch
79-108
: Normalize patch decorators to double quotes (Q000) and keep style consistentHouse style prefers double quotes. This is cosmetic but keeps diffs tidy.
-@patch('requests.get') +@patch("requests.get")Also applies to: 109-141, 142-165, 232-242
165-201
: Selective update logic looks correct; assert that unchanged projects are not passed to bulk_saveGood job updating only changed projects. You already assert the list length. As a micro-hardening, also assert that bulk_save is called with fields=["project_level_official"] for intent clarity.
- mock_bulk_save.assert_called_once() + mock_bulk_save.assert_called_once() + _, kwargs = mock_bulk_save.call_args + assert kwargs.get("fields") == ["project_level_official"]
220-231
: Level-mapping coverage is comprehensive; consider parametrization for brevityThe loop is fine. Using pytest.mark.parametrize would reduce boilerplate and make failures pinpoint which case failed.
I can rewrite this block with @pytest.mark.parametrize if you want a lighter test body.
243-275
: Integration test assertions align with command behavior; avoid brittle count wordingYou assert "Updated official levels for 1 projects". If that wording changes ("project(s)"), this will fail. Consider asserting the presence of the number and keyword separately or using a regex.
- assert "Updated official levels for 1 projects" in self.stdout.getvalue() + output = self.stdout.getvalue() + assert "Updated official levels for " in output and "1" in output
3-297
: Minor style/lint: trailing whitespace, long lines, and an unused local (F841/W293/E501)
- Several W293 instances (blank lines with spaces).
- A few long lines > 99 characters.
- mock_bulk_save assigned but not used in one test (F841).
Run ruff --fix and black on this file; I can push a formatting-only change if helpful.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (6)
3-11
: Remove unused importsStringIO and ProjectHealthMetrics are not used.
-import logging -from io import StringIO +import logging @@ -from apps.owasp.models.project_health_metrics import ProjectHealthMetrics
33-41
: Avoid callingselect_related()
without fields; narrow the query to the needed columnsYou only need name, level, and project_level_official. Reducing the selected columns lowers memory and DB IO.
- active_projects = Project.objects.filter(is_active=True).select_related() + active_projects = ( + Project.objects + .filter(is_active=True) + .only("name", "level", "project_level_official") + )
49-52
: Long line and Unicode marker: simplify and wrap for portability
- The line exceeds the style limit.
- The Unicode cross is fine, but avoid surprises in terminals. Consider plain ASCII and rely on style to emphasize.
- f"✗ {project.name}: Local={project.level}, Official={project.project_level_official}" + ( + f"{project.name}: Local={project.level}, " + f"Official={project.project_level_official}" + )
68-73
: Prefer clearer, wrapped messaging and avoid heavy Unicode glyphs in logsTwo nits:
- Wrap long lines.
- Consider avoiding "⚠" to ensure logs render well in all environments (CI, Windows terminals). Style.WARNING already provides emphasis.
- self.stdout.write(f"\n{self.style.WARNING('⚠ WARNING: Found ' + str(non_compliant_count) + ' non-compliant projects')}") + self.stdout.write( + "\n" + self.style.WARNING(f"WARNING: Found {non_compliant_count} non-compliant projects") + )
85-96
: Use of_meta
is acceptable in Django, but replace NOTICE and unify quotes
- Accessing _meta is a standard Django pattern; ignore SLF001 here.
- Django’s default style has SUCCESS/WARNING/ERROR; NOTICE may not exist depending on version. If this repo doesn’t augment color_style, this will raise at runtime.
If NOTICE isn’t guaranteed, switch to self.stdout.write for info lines or self.style.SUCCESS/ERROR/WARNING. Also prefer double quotes per code style.
- default_level = Project._meta.get_field('project_level_official').default + default_level = Project._meta.get_field("project_level_official").default @@ - self.stdout.write( - f"\n{self.style.NOTICE('ℹ INFO: ' + str(projects_without_official_level) + ' projects have default official levels')}" - ) - self.stdout.write("Run 'owasp_update_project_health_metrics' to sync official levels from OWASP GitHub.") + self.stdout.write( + f"\nINFO: {projects_without_official_level} projects have default official levels" + ) + self.stdout.write( + "Run 'owasp_update_project_health_metrics' to sync official levels from OWASP GitHub." + )
14-27
: Add basic tests for this command (acceptance criteria)Per #2039, we still need tests that execute this command and assert:
- Correct counts and compliance rate with mixed data.
- Verbose output on/off behavior.
- Info notice printed when default official levels are present.
I can scaffold backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py with pytest + call_command and MagicMocks.
backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py (5)
34-80
: Robust fetch with good validation; minor logging nitThe fetch path and validation look good. Two tiny nits:
- Prefer {e!s} over str(e) in logger extras (RUF010).
- After validating to list, consider early-returning empty dict in place of None to allow a distinct “empty payload” branch, if you want to distinguish failure vs. empty.
- logger.exception( + logger.exception( "Failed to fetch project levels", - extra={"url": OWASP_PROJECT_LEVELS_URL, "error": str(e)}, + extra={"url": OWASP_PROJECT_LEVELS_URL, "error": f"{e!s}"}, )
81-127
: Update mapping is correct; guard against missing/blank project namesTwo suggestions:
- project.name can theoretically be None/blank; guard before .strip() to avoid AttributeError.
- Use constants for mapped levels if available (e.g., ProjectLevel.LAB) to avoid typos creeping in.
- for project in Project.objects.filter(is_active=True): - normalized_project_name = project.name.strip().lower() + for project in Project.objects.filter(is_active=True): + if not project.name: + continue + normalized_project_name = project.name.strip().lower() @@ - mapped_level = level_mapping.get(official_level, "other") + mapped_level = level_mapping.get(official_level, "other")
129-142
: Handle empty payloads explicitly to avoid “success” phrasingWhen official_levels is an empty dict, you currently print a failure warning. That’s fine; optionally, you can tighten the condition and wording to be explicit.
- if official_levels: + if official_levels is not None and len(official_levels) > 0: self.stdout.write(f"Successfully fetched {len(official_levels)} official project levels") self.update_official_levels(official_levels) else: - self.stdout.write(self.style.WARNING("Failed to fetch official project levels, continuing without updates")) + self.stdout.write( + self.style.WARNING("Failed to fetch official project levels or received empty payload; continuing without updates") + )
166-176
: NOTICE style usage may not be portable across Django versionsUsing self.style.NOTICE can raise AttributeError in some Django versions. If your project defines NOTICE, ignore this. Otherwise, swap to plain write or SUCCESS/WARNING.
- self.stdout.write(self.style.NOTICE(f"Evaluating metrics for project: {project.name}")) + self.stdout.write(f"Evaluating metrics for project: {project.name}")
138-141
: Wrap long lines (E501)Two lines exceed the configured limit. Wrap to satisfy E501.
- self.stdout.write(f"Successfully fetched {len(official_levels)} official project levels") + self.stdout.write( + f"Successfully fetched {len(official_levels)} official project levels" + ) @@ - self.stdout.write(self.style.WARNING("Failed to fetch official project levels, continuing without updates")) + self.stdout.write( + self.style.WARNING( + "Failed to fetch official project levels, continuing without updates" + ) + )backend/tests/apps/owasp/management/commands/project_level_compliance_integration_test.py (9)
5-5
: Remove unused importjson
(F401)Not used in this module.
-from unittest.mock import MagicMock, patch -import json +from unittest.mock import MagicMock, patch
20-24
: Fixture should return rather than yield (PT022)There’s no teardown; use return to be explicit.
def _setup(self): """Set up test environment.""" self.stdout = StringIO() - yield + return
26-45
: Helper naming/docstrings and whitespace nits
- Docstrings should be imperative (D401).
- Remove trailing/blank-line whitespace (W291/W293).
- def create_mock_project(self, name, local_level, official_level=None): - """Helper to create a mock project with specified levels.""" + def create_mock_project(self, name, local_level, official_level=None): + """Create a mock project with specified levels.""" @@ - return project
80-132
: Great full-workflow coverage; avoid brittle exact phrasing in stdout assertionsMessages like "Updated official levels for 2 projects" can change. Consider checking the intent rather than exact wording (e.g., contains "Updated official levels" and "2").
- assert "Updated official levels for 2 projects" in output + assert "Updated official levels" in output and "2" in output
141-168
: Penalty logging assertions are meaningful; also assert that compliant projects don’t log penaltiesYou already assert the non-compliant path; add negative assertions for the compliant ones here too for stronger signal.
output = self.stdout.getvalue() + assert "compliance penalty to OWASP ZAP" not in output + assert "compliance penalty to OWASP Missing" not in output assert "compliance penalty to OWASP WebGoat" in output
169-211
: Level mapping test is solid; switch decorators to double quotes (Q000)Cosmetic: prefer double quotes to align with codebase style.
- @patch('requests.get') + @patch("requests.get")
256-307
: Targeted logging checks are helpful; consider parametrizing scenariosThe table-driven approach is nice; pytest.mark.parametrize would make this tighter and give better failure messages per case.
I can convert this to a parametrized test and keep your readable scenario names.
308-332
: Edge-case clamping: good assert; add explicit check for clamped percentage in stdoutYou assert score==0.0. Also assert the log reflects a 100% clamp for completeness.
output = self.stdout.getvalue() assert "Applied 100.0% compliance penalty" in output + assert "[Local: lab, Official: flagship]" in output
1-332
: General style fixes (Q000/W293/E501/F841)Multiple trailing whitespace, long lines, and a few unused locals (e.g., mock_project_bulk_save, mock_metrics_bulk_save, mock_scores_bulk_save). Running ruff --fix and black will resolve these cleanly.
Happy to push a formatting-only change across this test module.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (12)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
(1 hunks)backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py
(1 hunks)backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
(1 hunks)backend/apps/owasp/migrations/0049_remove_projecthealthrequirements_owasp_compliance_penalty_weight_0_100_and_more.py
(1 hunks)backend/apps/owasp/models/project.py
(2 hunks)backend/apps/owasp/models/project_health_requirements.py
(2 hunks)backend/apps/owasp/utils/__init__.py
(1 hunks)backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py
(1 hunks)backend/tests/apps/owasp/management/commands/owasp_update_project_health_metrics_test.py
(2 hunks)backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py
(1 hunks)backend/tests/apps/owasp/management/commands/project_level_compliance_integration_test.py
(1 hunks)backend/tests/apps/owasp/models/project_test.py
(1 hunks)
✅ Files skipped from review due to trivial changes (1)
- backend/apps/owasp/utils/init.py
🚧 Files skipped from review as they are similar to previous changes (1)
- backend/apps/owasp/models/project_health_requirements.py
🧰 Additional context used
🧬 Code graph analysis (9)
backend/apps/owasp/models/project.py (1)
backend/apps/owasp/models/enums/project.py (1)
ProjectLevel
(37-44)
backend/tests/apps/owasp/models/project_test.py (2)
backend/apps/owasp/models/enums/project.py (1)
ProjectLevel
(37-44)backend/apps/owasp/models/project.py (2)
Project
(31-385)is_level_compliant
(162-164)
backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py (1)
backend/apps/owasp/models/project.py (2)
Project
(31-385)bulk_save
(352-360)
backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py (2)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
Command
(14-96)backend/apps/owasp/models/project.py (2)
Project
(31-385)is_level_compliant
(162-164)
backend/tests/apps/owasp/management/commands/owasp_update_project_health_metrics_test.py (2)
backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py (2)
fetch_official_project_levels
(34-79)update_official_levels
(81-127)backend/apps/owasp/models/project.py (1)
Project
(31-385)
backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py (3)
backend/apps/owasp/models/project_health_metrics.py (1)
ProjectHealthMetrics
(16-235)backend/apps/owasp/models/project_health_requirements.py (1)
ProjectHealthRequirements
(10-70)backend/apps/owasp/models/project.py (3)
is_level_compliant
(162-164)is_funding_requirements_compliant
(150-152)is_leader_requirements_compliant
(155-159)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (3)
backend/apps/owasp/models/project.py (2)
Project
(31-385)is_level_compliant
(162-164)backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py (3)
Command
(17-176)add_arguments
(20-32)handle
(129-176)backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (2)
Command
(9-84)handle
(12-84)
backend/tests/apps/owasp/management/commands/project_level_compliance_integration_test.py (5)
backend/apps/owasp/models/project.py (4)
Project
(31-385)is_funding_requirements_compliant
(150-152)is_leader_requirements_compliant
(155-159)is_level_compliant
(162-164)backend/apps/owasp/models/project_health_metrics.py (1)
ProjectHealthMetrics
(16-235)backend/apps/owasp/models/project_health_requirements.py (1)
ProjectHealthRequirements
(10-70)backend/tests/apps/owasp/management/commands/owasp_update_project_health_metrics_test.py (1)
_setup
(17-29)backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py (1)
_setup
(16-34)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
backend/apps/owasp/models/project.py (1)
is_level_compliant
(162-164)
🪛 Ruff (0.12.2)
backend/apps/owasp/management/commands/owasp_update_project_health_metrics.py
40-40: Missing blank line after last section ("Returns")
Add blank line after "Returns"
(D413)
72-72: Consider moving this statement to an else
block
(TRY300)
83-83: Blank line contains whitespace
Remove whitespace from blank line
(W293)
86-86: Blank line contains whitespace
Remove whitespace from blank line
(W293)
87-87: Missing blank line after last section ("Returns")
Add blank line after "Returns"
(D413)
92-92: Blank line contains whitespace
Remove whitespace from blank line
(W293)
98-98: Blank line contains whitespace
Remove whitespace from blank line
(W293)
115-115: Blank line contains whitespace
Remove whitespace from blank line
(W293)
120-120: Blank line contains whitespace
Remove whitespace from blank line
(W293)
126-126: Blank line contains whitespace
Remove whitespace from blank line
(W293)
132-132: Blank line contains whitespace
Remove whitespace from blank line
(W293)
138-138: Line too long (105 > 99)
(E501)
141-141: Line too long (124 > 99)
(E501)
142-142: Blank line contains whitespace
Remove whitespace from blank line
(W293)
backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py
22-22: No teardown in fixture _setup
, use return
instead of yield
Replace yield
with return
(PT022)
25-25: First line of docstring should be in imperative mood: "Helper to create a mock project."
(D401)
38-38: Boolean positional value in function call
(FBT003)
39-39: Boolean positional value in function call
(FBT003)
40-40: Boolean positional value in function call
(FBT003)
45-45: Blank line contains whitespace
Remove whitespace from blank line
(W293)
47-47: Blank line contains whitespace
Remove whitespace from blank line
(W293)
49-49: Blank line contains whitespace
Remove whitespace from blank line
(W293)
51-51: Blank line contains whitespace
Remove whitespace from blank line
(W293)
64-64: Boolean positional value in function call
(FBT003)
65-65: Boolean positional value in function call
(FBT003)
66-66: Boolean positional value in function call
(FBT003)
71-71: Blank line contains whitespace
Remove whitespace from blank line
(W293)
73-73: Blank line contains whitespace
Remove whitespace from blank line
(W293)
75-75: Blank line contains whitespace
Remove whitespace from blank line
(W293)
77-77: Blank line contains whitespace
Remove whitespace from blank line
(W293)
84-84: Blank line contains whitespace
Remove whitespace from blank line
(W293)
92-92: Boolean positional value in function call
(FBT003)
93-93: Boolean positional value in function call
(FBT003)
98-98: Blank line contains whitespace
Remove whitespace from blank line
(W293)
100-100: Blank line contains whitespace
Remove whitespace from blank line
(W293)
102-102: Blank line contains whitespace
Remove whitespace from blank line
(W293)
104-104: Blank line contains whitespace
Remove whitespace from blank line
(W293)
113-113: Blank line contains whitespace
Remove whitespace from blank line
(W293)
115-115: Blank line contains whitespace
Remove whitespace from blank line
(W293)
117-117: Blank line contains whitespace
Remove whitespace from blank line
(W293)
119-119: Blank line contains whitespace
Remove whitespace from blank line
(W293)
129-129: Boolean positional value in function call
(FBT003)
130-130: Boolean positional value in function call
(FBT003)
130-130: Line too long (102 > 99)
(E501)
135-135: Blank line contains whitespace
Remove whitespace from blank line
(W293)
139-139: Blank line contains whitespace
Remove whitespace from blank line
(W293)
141-141: Blank line contains whitespace
Remove whitespace from blank line
(W293)
143-143: Blank line contains whitespace
Remove whitespace from blank line
(W293)
145-145: String contains ambiguous ℹ
(INFORMATION SOURCE). Did you mean i
(LATIN SMALL LETTER I)?
(RUF001)
157-157: Line too long (105 > 99)
(E501)
159-159: Line too long (110 > 99)
(E501)
165-165: Blank line contains whitespace
Remove whitespace from blank line
(W293)
168-168: Blank line contains whitespace
Remove whitespace from blank line
(W293)
170-170: Blank line contains whitespace
Remove whitespace from blank line
(W293)
172-172: Blank line contains whitespace
Remove whitespace from blank line
(W293)
175-175: No newline at end of file
Add trailing newline
(W292)
backend/tests/apps/owasp/management/commands/owasp_update_project_health_metrics_test.py
3-3: json
imported but unused
Remove unused import: json
(F401)
79-79: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
109-109: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
118-118: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
130-130: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
142-142: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
185-185: Blank line contains whitespace
Remove whitespace from blank line
(W293)
187-187: Blank line contains whitespace
Remove whitespace from blank line
(W293)
189-189: Blank line contains whitespace
Remove whitespace from blank line
(W293)
194-194: Blank line contains whitespace
Remove whitespace from blank line
(W293)
222-222: Local variable mock_bulk_save
is assigned to but never used
Remove assignment to unused variable mock_bulk_save
(F841)
223-223: Blank line contains whitespace
Remove whitespace from blank line
(W293)
226-226: Blank line contains whitespace
Remove whitespace from blank line
(W293)
229-229: Blank line contains whitespace
Remove whitespace from blank line
(W293)
232-232: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
259-259: Line too long (122 > 99)
(E501)
261-261: Blank line contains whitespace
Remove whitespace from blank line
(W293)
263-263: Blank line contains whitespace
Remove whitespace from blank line
(W293)
265-265: Blank line contains whitespace
Remove whitespace from blank line
(W293)
270-270: Blank line contains whitespace
Remove whitespace from blank line
(W293)
291-291: Line too long (114 > 99)
(E501)
293-293: Blank line contains whitespace
Remove whitespace from blank line
(W293)
295-295: Blank line contains whitespace
Remove whitespace from blank line
(W293)
297-297: Blank line contains whitespace
Remove whitespace from blank line
(W293)
backend/apps/owasp/migrations/0049_remove_projecthealthrequirements_owasp_compliance_penalty_weight_0_100_and_more.py
10-10: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
10-10: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
15-15: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
16-16: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
17-17: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
17-17: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py
109-109: Blank line contains whitespace
Remove whitespace from blank line
(W293)
113-113: Blank line contains whitespace
Remove whitespace from blank line
(W293)
119-119: Blank line contains whitespace
Remove whitespace from blank line
(W293)
123-123: Blank line contains whitespace
Remove whitespace from blank line
(W293)
126-126: Blank line contains whitespace
Remove whitespace from blank line
(W293)
130-130: Blank line contains whitespace
Remove whitespace from blank line
(W293)
139-139: Found commented-out code
Remove commented-out code
(ERA001)
141-141: Local variable expected_score
is assigned to but never used
Remove assignment to unused variable expected_score
(F841)
142-142: Blank line contains whitespace
Remove whitespace from blank line
(W293)
146-146: Blank line contains whitespace
Remove whitespace from blank line
(W293)
150-150: Assertion should be broken down into multiple parts
Break down assertion into multiple parts
(PT018)
176-176: Blank line contains whitespace
Remove whitespace from blank line
(W293)
180-180: Blank line contains whitespace
Remove whitespace from blank line
(W293)
186-186: Blank line contains whitespace
Remove whitespace from blank line
(W293)
189-189: Blank line contains whitespace
Remove whitespace from blank line
(W293)
192-192: Blank line contains whitespace
Remove whitespace from blank line
(W293)
196-196: Blank line contains whitespace
Remove whitespace from blank line
(W293)
202-202: Local variable expected_score
is assigned to but never used
Remove assignment to unused variable expected_score
(F841)
203-203: Blank line contains whitespace
Remove whitespace from blank line
(W293)
206-206: Blank line contains whitespace
Remove whitespace from blank line
(W293)
215-215: Blank line contains whitespace
Remove whitespace from blank line
(W293)
219-219: Line too long (102 > 99)
(E501)
224-224: Blank line contains whitespace
Remove whitespace from blank line
(W293)
230-230: Blank line contains whitespace
Remove whitespace from blank line
(W293)
233-233: Blank line contains whitespace
Remove whitespace from blank line
(W293)
236-236: Blank line contains whitespace
Remove whitespace from blank line
(W293)
240-240: Blank line contains whitespace
Remove whitespace from blank line
(W293)
246-246: Local variable expected_base_score
is assigned to but never used
Remove assignment to unused variable expected_base_score
(F841)
249-249: Blank line contains whitespace
Remove whitespace from blank line
(W293)
258-258: Blank line contains whitespace
Remove whitespace from blank line
(W293)
262-262: Line too long (102 > 99)
(E501)
267-267: Blank line contains whitespace
Remove whitespace from blank line
(W293)
273-273: Blank line contains whitespace
Remove whitespace from blank line
(W293)
276-276: Blank line contains whitespace
Remove whitespace from blank line
(W293)
279-279: Blank line contains whitespace
Remove whitespace from blank line
(W293)
283-283: Blank line contains whitespace
Remove whitespace from blank line
(W293)
295-295: Blank line contains whitespace
Remove whitespace from blank line
(W293)
299-299: Line too long (102 > 99)
(E501)
304-304: Blank line contains whitespace
Remove whitespace from blank line
(W293)
310-310: Blank line contains whitespace
Remove whitespace from blank line
(W293)
313-313: Blank line contains whitespace
Remove whitespace from blank line
(W293)
320-320: Blank line contains whitespace
Remove whitespace from blank line
(W293)
323-323: Blank line contains whitespace
Remove whitespace from blank line
(W293)
327-327: Blank line contains whitespace
Remove whitespace from blank line
(W293)
330-330: Blank line contains whitespace
Remove whitespace from blank line
(W293)
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py
4-4: io.StringIO
imported but unused
Remove unused import: io.StringIO
(F401)
9-9: apps.owasp.models.project_health_metrics.ProjectHealthMetrics
imported but unused
Remove unused import: apps.owasp.models.project_health_metrics.ProjectHealthMetrics
(F401)
16-16: Blank line contains whitespace
Remove whitespace from blank line
(W293)
30-30: Blank line contains whitespace
Remove whitespace from blank line
(W293)
32-32: Blank line contains whitespace
Remove whitespace from blank line
(W293)
35-35: Blank line contains whitespace
Remove whitespace from blank line
(W293)
38-38: Blank line contains whitespace
Remove whitespace from blank line
(W293)
50-50: Line too long (109 > 99)
(E501)
53-53: Blank line contains whitespace
Remove whitespace from blank line
(W293)
59-59: Blank line contains whitespace
Remove whitespace from blank line
(W293)
67-67: Blank line contains whitespace
Remove whitespace from blank line
(W293)
69-69: Line too long (132 > 99)
(E501)
70-70: Line too long (109 > 99)
(E501)
73-73: Blank line contains whitespace
Remove whitespace from blank line
(W293)
84-84: Blank line contains whitespace
Remove whitespace from blank line
(W293)
86-86: Private member accessed: _meta
(SLF001)
86-86: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
91-91: Blank line contains whitespace
Remove whitespace from blank line
(W293)
94-94: String contains ambiguous ℹ
(INFORMATION SOURCE). Did you mean i
(LATIN SMALL LETTER I)?
(RUF001)
94-94: Line too long (134 > 99)
(E501)
96-96: Line too long (117 > 99)
(E501)
96-96: No newline at end of file
Add trailing newline
(W292)
backend/tests/apps/owasp/management/commands/project_level_compliance_integration_test.py
5-5: json
imported but unused
Remove unused import: json
(F401)
24-24: No teardown in fixture _setup
, use return
instead of yield
Replace yield
with return
(PT022)
27-27: First line of docstring should be in imperative mood: "Helper to create a mock project with specified levels."
(D401)
33-33: Blank line contains whitespace
Remove whitespace from blank line
(W293)
35-35: Trailing whitespace
Remove trailing whitespace
(W291)
43-43: Blank line contains whitespace
Remove whitespace from blank line
(W293)
47-47: First line of docstring should be in imperative mood: "Helper to create a mock health metric for a project."
(D401)
50-50: Blank line contains whitespace
Remove whitespace from blank line
(W293)
54-54: Line too long (102 > 99)
(E501)
58-58: Blank line contains whitespace
Remove whitespace from blank line
(W293)
61-61: Blank line contains whitespace
Remove whitespace from blank line
(W293)
65-65: First line of docstring should be in imperative mood: "Helper to create mock health requirements."
(D401)
69-69: Blank line contains whitespace
Remove whitespace from blank line
(W293)
73-73: Line too long (102 > 99)
(E501)
77-77: Blank line contains whitespace
Remove whitespace from blank line
(W293)
80-80: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
82-82: Line too long (109 > 99)
(E501)
94-94: Line too long (115 > 99)
(E501)
95-95: Line too long (122 > 99)
(E501)
96-96: Line too long (105 > 99)
(E501)
116-116: Local variable mock_project_bulk_save
is assigned to but never used
Remove assignment to unused variable mock_project_bulk_save
(F841)
117-117: Local variable mock_metrics_bulk_save
is assigned to but never used
Remove assignment to unused variable mock_metrics_bulk_save
(F841)
117-117: Line too long (122 > 99)
(E501)
119-119: Blank line contains whitespace
Remove whitespace from blank line
(W293)
121-121: Blank line contains whitespace
Remove whitespace from blank line
(W293)
123-123: Blank line contains whitespace
Remove whitespace from blank line
(W293)
127-127: Line too long (102 > 99)
(E501)
128-128: Blank line contains whitespace
Remove whitespace from blank line
(W293)
140-140: Blank line contains whitespace
Remove whitespace from blank line
(W293)
141-141: Line too long (124 > 99)
(E501)
142-142: Line too long (129 > 99)
(E501)
143-143: Local variable mock_scores_bulk_save
is assigned to but never used
Remove assignment to unused variable mock_scores_bulk_save
(F841)
143-143: Line too long (121 > 99)
(E501)
145-145: Blank line contains whitespace
Remove whitespace from blank line
(W293)
148-148: Blank line contains whitespace
Remove whitespace from blank line
(W293)
150-150: Blank line contains whitespace
Remove whitespace from blank line
(W293)
153-153: Blank line contains whitespace
Remove whitespace from blank line
(W293)
157-157: Blank line contains whitespace
Remove whitespace from blank line
(W293)
160-160: Blank line contains whitespace
Remove whitespace from blank line
(W293)
163-163: Blank line contains whitespace
Remove whitespace from blank line
(W293)
166-166: Assertion should be broken down into multiple parts
Break down assertion into multiple parts
(PT018)
169-169: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
192-192: Local variable mock_bulk_save
is assigned to but never used
Remove assignment to unused variable mock_bulk_save
(F841)
195-195: Blank line contains whitespace
Remove whitespace from blank line
(W293)
197-197: Blank line contains whitespace
Remove whitespace from blank line
(W293)
199-199: Blank line contains whitespace
Remove whitespace from blank line
(W293)
205-205: Blank line contains whitespace
Remove whitespace from blank line
(W293)
212-212: Single quotes found but double quotes preferred
Replace single quotes with double quotes
(Q000)
223-223: Blank line contains whitespace
Remove whitespace from blank line
(W293)
225-225: Blank line contains whitespace
Remove whitespace from blank line
(W293)
227-227: Blank line contains whitespace
Remove whitespace from blank line
(W293)
232-232: Blank line contains whitespace
Remove whitespace from blank line
(W293)
243-243: Blank line contains whitespace
Remove whitespace from blank line
(W293)
245-245: Blank line contains whitespace
Remove whitespace from blank line
(W293)
247-247: Blank line contains whitespace
Remove whitespace from blank line
(W293)
252-252: Blank line contains whitespace
Remove whitespace from blank line
(W293)
268-268: Blank line contains whitespace
Remove whitespace from blank line
(W293)
273-273: Blank line contains whitespace
Remove whitespace from blank line
(W293)
286-286: Line too long (124 > 99)
(E501)
287-287: Line too long (129 > 99)
(E501)
290-290: Blank line contains whitespace
Remove whitespace from blank line
(W293)
293-293: Blank line contains whitespace
Remove whitespace from blank line
(W293)
295-295: Blank line contains whitespace
Remove whitespace from blank line
(W293)
297-297: Blank line contains whitespace
Remove whitespace from blank line
(W293)
301-301: Blank line contains whitespace
Remove whitespace from blank line
(W293)
314-314: Blank line contains whitespace
Remove whitespace from blank line
(W293)
316-316: Line too long (102 > 99)
(E501)
317-317: Blank line contains whitespace
Remove whitespace from blank line
(W293)
318-318: Line too long (124 > 99)
(E501)
319-319: Line too long (129 > 99)
(E501)
322-322: Blank line contains whitespace
Remove whitespace from blank line
(W293)
325-325: Blank line contains whitespace
Remove whitespace from blank line
(W293)
327-327: Blank line contains whitespace
Remove whitespace from blank line
(W293)
330-330: Blank line contains whitespace
Remove whitespace from blank line
(W293)
332-332: No newline at end of file
Add trailing newline
(W292)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
69-69: Line too long (101 > 99)
(E501)
71-71: Line too long (109 > 99)
(E501)
🔇 Additional comments (8)
backend/apps/owasp/migrations/0049_remove_projecthealthrequirements_owasp_compliance_penalty_weight_0_100_and_more.py (1)
13-19
: DB-level constraints and validators: nice hardeningAltering the field to FloatField with min/max validators and a clearer verbose_name is a good complement to the runtime clamp. LGTM.
backend/tests/apps/owasp/management/commands/owasp_detect_project_level_compliance_test.py (3)
34-59
: Good happy-path coverage for “all compliant” scenarioAssertions validate headers, counts, rate, and success line. Looks good.
89-108
: Verbose mode test is useful; assert header toggle if applicableIf the command prints an initial “Analyzing…” header, assert its presence here too to ensure stdout wiring remains intact.
109-125
: No projects scenario handled; LGTMZero-counts and success message assertions look correct.
backend/tests/apps/owasp/management/commands/owasp_update_project_health_scores_test.py (1)
254-290
: Max-penalty case is crisp; LGTMThe 100% clamping to 0.0 is asserted correctly and aligns with the command’s logic. No changes suggested.
backend/apps/owasp/management/commands/owasp_detect_project_level_compliance.py (1)
60-67
: Great, succinct summary blockThe summary math and formatting are clear. No changes suggested.
backend/tests/apps/owasp/management/commands/project_level_compliance_integration_test.py (2)
212-235
: API failure path: LGTMThe test ensures the command proceeds to metrics evaluation and preserves official level on failure. Looks good.
236-255
: Skip flag path is correctEnsures the fetch is skipped and processing continues. Good coverage.
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
Show resolved
Hide resolved
…alth_scores.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
49-58
: Compute score only after guarding requirements; remove early direct dict indexing to avoid KeyError and stale requirementsYou added a safe .get(...) guard (lines 60-69), but the code still does a direct index lookup at Line 49 and computes the score (Lines 51-58) before the guard runs. This reintroduces the original KeyError risk and means the guard is too late. Unify the retrieval and scoring under the guarded path.
Apply this refactor to merge retrieval + guard and then compute the score:
- requirements = project_health_requirements[metric.project.level] - - score = 0.0 - for field, weight in forward_fields.items(): - if int(getattr(metric, field)) >= int(getattr(requirements, field)): - score += weight - - for field, weight in backward_fields.items(): - if int(getattr(metric, field)) <= int(getattr(requirements, field)): - score += weight - - # Fetch requirements for this project level, skip if missing - requirements = project_health_requirements.get(metric.project.level) - if requirements is None: - self.stdout.write( - self.style.WARNING( - f"Missing ProjectHealthRequirements for level '{metric.project.level}' — " - f"skipping scoring for {metric.project.name}" - ) - ) - continue + # Fetch requirements for this project level, skip if missing + requirements = project_health_requirements.get(metric.project.level) + if requirements is None: + self.stdout.write( + self.style.WARNING( + f"Missing ProjectHealthRequirements for level '{metric.project.level}' — " + f"skipping scoring for {metric.project.name}" + ) + ) + continue + + # Calculate the score based on requirements. + score = 0.0 + for field, weight in forward_fields.items(): + if int(getattr(metric, field)) >= int(getattr(requirements, field)): + score += weight + + for field, weight in backward_fields.items(): + if int(getattr(metric, field)) <= int(getattr(requirements, field)): + score += weightAlso applies to: 60-69
🧹 Nitpick comments (1)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
79-85
: Wrap long log lines to satisfy Ruff E501 and improve readabilityThe warning message exceeds the 99-char limit (Ruff E501). Build the message in a variable with implicit string concatenation or split across multiple f-strings.
- self.stdout.write( - self.style.WARNING( - f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " - f"(penalty: {penalty_amount:.2f}, final score: {score:.2f}) " - f"[Local: {metric.project.level}, Official: {metric.project.project_level_official}]" - ) - ) + msg = ( + f"Applied {penalty_percentage}% compliance penalty to {metric.project.name} " + f"(penalty: {penalty_amount:.2f}, final score: {score:.2f}) " + f"[Local: {metric.project.level}, " + f"Official: {metric.project.project_level_official}]" + ) + self.stdout.write(self.style.WARNING(msg))
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (1)
backend/apps/owasp/models/project.py (1)
is_level_compliant
(162-164)
🪛 Ruff (0.12.2)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py
80-80: Line too long (101 > 99)
(E501)
82-82: Line too long (109 > 99)
(E501)
🔇 Additional comments (2)
backend/apps/owasp/management/commands/owasp_update_project_health_scores.py (2)
73-77
: Nice: penalty and final score clamped to valid boundsGood defensive coding: clamping the penalty to [0, 100] and the final score to [0, 100] prevents misconfigurations from corrupting scores.
Also applies to: 86-86
71-78
: Ignore incorrect method call onis_level_compliant
Theis_level_compliant
accessor is defined as a@property
onProject
(backend/apps/owasp/models/project.py, lines 161–163), so using it without parentheses is correct. No change is needed to call syntax, and altering it to a method call would break existing behavior and tests.Likely an incorrect or invalid review comment.
Proposed change
Resolves #2039
Add the PR description here.
Checklist
make check-test
locally; all checks and tests passed.