$ [MASTER]
# Ignore generated files and migrations
ignore=migrations,generated [MESSAGES CONTROL]
# Disable checks handled by other tools
-weight: 500;">disable= C0114, # missing-module-docstring (review-enforced) C0115, # missing-class-docstring C0116, # missing-function-docstring W0611, # unused-import (handled by Ruff in pre-commit) R0903, # too-few-public-methods (noisy on data classes) [FORMAT]
max-line-length=120 [DESIGN]
max-args=7
max-locals=15
max-returns=6
max-branches=12
max-statements=50
[MASTER]
# Ignore generated files and migrations
ignore=migrations,generated [MESSAGES CONTROL]
# Disable checks handled by other tools
-weight: 500;">disable= C0114, # missing-module-docstring (review-enforced) C0115, # missing-class-docstring C0116, # missing-function-docstring W0611, # unused-import (handled by Ruff in pre-commit) R0903, # too-few-public-methods (noisy on data classes) [FORMAT]
max-line-length=120 [DESIGN]
max-args=7
max-locals=15
max-returns=6
max-branches=12
max-statements=50
[MASTER]
# Ignore generated files and migrations
ignore=migrations,generated [MESSAGES CONTROL]
# Disable checks handled by other tools
-weight: 500;">disable= C0114, # missing-module-docstring (review-enforced) C0115, # missing-class-docstring C0116, # missing-function-docstring W0611, # unused-import (handled by Ruff in pre-commit) R0903, # too-few-public-methods (noisy on data classes) [FORMAT]
max-line-length=120 [DESIGN]
max-args=7
max-locals=15
max-returns=6
max-branches=12
max-statements=50
# Bandit B303: use of insecure MD5 hash
def hash_password(password: str) -> str: return hashlib.md5(password.encode()).hexdigest() # Bandit B602: subprocess call with shell=True
def run_command(user_input: str) -> str: result = subprocess.check_output(user_input, shell=True) return result.decode() # Bandit B301: pickle deserialization (unsafe with untrusted data)
def load_model(data: bytes): return pickle.loads(data) # Bandit B105: hardcoded password string
DATABASE_PASSWORD = "prod_secret_2024"
# Bandit B303: use of insecure MD5 hash
def hash_password(password: str) -> str: return hashlib.md5(password.encode()).hexdigest() # Bandit B602: subprocess call with shell=True
def run_command(user_input: str) -> str: result = subprocess.check_output(user_input, shell=True) return result.decode() # Bandit B301: pickle deserialization (unsafe with untrusted data)
def load_model(data: bytes): return pickle.loads(data) # Bandit B105: hardcoded password string
DATABASE_PASSWORD = "prod_secret_2024"
# Bandit B303: use of insecure MD5 hash
def hash_password(password: str) -> str: return hashlib.md5(password.encode()).hexdigest() # Bandit B602: subprocess call with shell=True
def run_command(user_input: str) -> str: result = subprocess.check_output(user_input, shell=True) return result.decode() # Bandit B301: pickle deserialization (unsafe with untrusted data)
def load_model(data: bytes): return pickle.loads(data) # Bandit B105: hardcoded password string
DATABASE_PASSWORD = "prod_secret_2024"
# .prospector.yaml
strictness: medium
doc-warnings: false
test-warnings: false ignore-paths: - migrations - tests - docs pep8: options: max-line-length: 120 mccabe: options: max-complexity: 10
# .prospector.yaml
strictness: medium
doc-warnings: false
test-warnings: false ignore-paths: - migrations - tests - docs pep8: options: max-line-length: 120 mccabe: options: max-complexity: 10
# .prospector.yaml
strictness: medium
doc-warnings: false
test-warnings: false ignore-paths: - migrations - tests - docs pep8: options: max-line-length: 120 mccabe: options: max-complexity: 10
# pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=xml --cov-report=term-missing"
testpaths = ["tests"] [tool.coverage.run]
source = ["src"]
omit = [ "src/*/migrations/*", "src/*/tests/*", "src/manage.py", "src/*/conftest.py",
] [tool.coverage.report]
exclude_lines = [ "pragma: no cover", "def __repr__", "if TYPE_CHECKING:", "raise NotImplementedError", "if __name__ == .__main__.:",
]
# pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=xml --cov-report=term-missing"
testpaths = ["tests"] [tool.coverage.run]
source = ["src"]
omit = [ "src/*/migrations/*", "src/*/tests/*", "src/manage.py", "src/*/conftest.py",
] [tool.coverage.report]
exclude_lines = [ "pragma: no cover", "def __repr__", "if TYPE_CHECKING:", "raise NotImplementedError", "if __name__ == .__main__.:",
]
# pyproject.toml
[tool.pytest.ini_options]
addopts = "--cov=src --cov-report=xml --cov-report=term-missing"
testpaths = ["tests"] [tool.coverage.run]
source = ["src"]
omit = [ "src/*/migrations/*", "src/*/tests/*", "src/manage.py", "src/*/conftest.py",
] [tool.coverage.report]
exclude_lines = [ "pragma: no cover", "def __repr__", "if TYPE_CHECKING:", "raise NotImplementedError", "if __name__ == .__main__.:",
]
name: Python Tests and Coverage on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install dependencies run: -weight: 500;">pip -weight: 500;">install -r requirements.txt pytest pytest-cov - name: Run tests with coverage run: pytest - name: Upload coverage to Codacy uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: coverage.xml
name: Python Tests and Coverage on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install dependencies run: -weight: 500;">pip -weight: 500;">install -r requirements.txt pytest pytest-cov - name: Run tests with coverage run: pytest - name: Upload coverage to Codacy uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: coverage.xml
name: Python Tests and Coverage on: push: branches: [main] pull_request: jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: python-version: "3.12" - name: Install dependencies run: -weight: 500;">pip -weight: 500;">install -r requirements.txt pytest pytest-cov - name: Run tests with coverage run: pytest - name: Upload coverage to Codacy uses: codacy/codacy-coverage-reporter-action@v1 with: project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} coverage-reports: coverage.xml
# pyproject.toml - Django-specific coverage settings
[tool.coverage.run]
source = ["apps"]
omit = [ "*/migrations/*", "*/admin.py", # unless you test admin customizations "*/apps.py", "manage.py", "conftest.py", "*/settings/*",
] [tool.coverage.report]
fail_under = 80
# pyproject.toml - Django-specific coverage settings
[tool.coverage.run]
source = ["apps"]
omit = [ "*/migrations/*", "*/admin.py", # unless you test admin customizations "*/apps.py", "manage.py", "conftest.py", "*/settings/*",
] [tool.coverage.report]
fail_under = 80
# pyproject.toml - Django-specific coverage settings
[tool.coverage.run]
source = ["apps"]
omit = [ "*/migrations/*", "*/admin.py", # unless you test admin customizations "*/apps.py", "manage.py", "conftest.py", "*/settings/*",
] [tool.coverage.report]
fail_under = 80
- name: Run Django tests with coverage env: DJANGO_SETTINGS_MODULE: myproject.settings.test run: pytest --ds=myproject.settings.test
- name: Run Django tests with coverage env: DJANGO_SETTINGS_MODULE: myproject.settings.test run: pytest --ds=myproject.settings.test
- name: Run Django tests with coverage env: DJANGO_SETTINGS_MODULE: myproject.settings.test run: pytest --ds=myproject.settings.test
# Bandit flags: raw SQL with potential injection
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'") # Safe: parameterized query
User.objects.raw("SELECT * FROM users WHERE name = %s", [name]) # Bandit flags: extra() with user input
User.objects.extra(where=[f"name = '{name}'"])
# Bandit flags: raw SQL with potential injection
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'") # Safe: parameterized query
User.objects.raw("SELECT * FROM users WHERE name = %s", [name]) # Bandit flags: extra() with user input
User.objects.extra(where=[f"name = '{name}'"])
# Bandit flags: raw SQL with potential injection
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'") # Safe: parameterized query
User.objects.raw("SELECT * FROM users WHERE name = %s", [name]) # Bandit flags: extra() with user input
User.objects.extra(where=[f"name = '{name}'"])
from flask import Flask app = Flask(__name__) # Bandit B201: Flask debug mode in production
app.run(debug=True) # Bandit B105: hardcoded secret key
app.secret_key = "dev_key_replace_me" # Bandit B608: SQL injection (with Flask-SQLAlchemy bypassed)
def get_user(username): query = f"SELECT * FROM users WHERE username = '{username}'" return db.engine.execute(query)
from flask import Flask app = Flask(__name__) # Bandit B201: Flask debug mode in production
app.run(debug=True) # Bandit B105: hardcoded secret key
app.secret_key = "dev_key_replace_me" # Bandit B608: SQL injection (with Flask-SQLAlchemy bypassed)
def get_user(username): query = f"SELECT * FROM users WHERE username = '{username}'" return db.engine.execute(query)
from flask import Flask app = Flask(__name__) # Bandit B201: Flask debug mode in production
app.run(debug=True) # Bandit B105: hardcoded secret key
app.secret_key = "dev_key_replace_me" # Bandit B608: SQL injection (with Flask-SQLAlchemy bypassed)
def get_user(username): query = f"SELECT * FROM users WHERE username = '{username}'" return db.engine.execute(query)
# .codacy.yml
---
engines: pylint: enabled: true bandit: enabled: true prospector: enabled: true radon: enabled: true exclude_paths: - "migrations/**" - "tests/**" - "docs/**" - "*.pyc" - "setup.py"
# .codacy.yml
---
engines: pylint: enabled: true bandit: enabled: true prospector: enabled: true radon: enabled: true exclude_paths: - "migrations/**" - "tests/**" - "docs/**" - "*.pyc" - "setup.py"
# .codacy.yml
---
engines: pylint: enabled: true bandit: enabled: true prospector: enabled: true radon: enabled: true exclude_paths: - "migrations/**" - "tests/**" - "docs/**" - "*.pyc" - "setup.py" - Pylint - deep static analysis covering code quality, style violations, potential bugs, and refactoring opportunities
- Bandit - Python-specific security vulnerability detection
- Prospector - a meta-tool that runs multiple Python analysis tools and aggregates their output
- Radon - complexity metrics including cyclomatic complexity, cognitive complexity, and Maintainability Index (MI) - SQL injection via string formatting in raw queries
- Shell injection via subprocess.call(shell=True) or os.system()
- Command injection through user-controlled arguments to shell commands - Use of weak hash algorithms: MD5, SHA1 for security purposes
- Use of insecure random number generators (random module instead of secrets)
- Hardcoded cryptographic keys or seeds - eval() and exec() with user-controlled input
- pickle.loads() with untrusted data (a common ML/data science vulnerability)
- yaml.load() without Loader=yaml.SafeLoader
- subprocess calls with shell=True - Hardcoded passwords, API keys, and tokens in source code
- Database connection strings with credentials embedded - HTTPS certificate verification disabled with verify=False
- Insecure use of tempfile functions
- Overly permissive file permissions - pyflakes integration: catches undefined names, unused imports, and redefined unused names with fewer false positives than Pylint's equivalent checks
- dodgy checks: flags obvious credential leaks - not as comprehensive as Bandit's secret detection, but catches the clearest cases
- Mccabe complexity: an alternative complexity metric to Radon that measures cyclomatic complexity at the function level - Minimum coverage threshold: Block PRs where overall coverage drops below (e.g., 80%)
- Coverage delta: Block PRs that reduce coverage by more than a specified percentage (e.g., 5%)
- New code coverage: Require that new code introduced in the PR meets a minimum threshold (e.g., 90%) - Block if new issues introduced with severity HIGH or CRITICAL: yes (no new high-severity issues allowed)
- Block if new issues introduced with severity MEDIUM: optional (warn but do not block for medium-severity) - Minimum overall coverage: 75% (adjust to your current baseline, ratchet up over time)
- New code coverage: 80% (new code should be more thoroughly tested than legacy)
- Coverage delta: block if coverage drops more than 5% in a single PR - Codacy does not provide a direct complexity gate at the PR level, but it tracks complexity trends in the dashboard. Use the issue system to flag HIGH complexity (F-grade Radon functions) and treat them as issues to resolve. - Block if new duplication exceeds 5% of the PR changeset - .pylintrc or pyproject.toml [tool.pylint] for Pylint
- .bandit or pyproject.toml [tool.bandit] for Bandit
- .prospector.yaml for Prospector - Ruff in pre-commit hooks for instant linting and formatting feedback (Codacy does not integrate Ruff, so running it separately in pre-commit gives developers fast feedback before CI runs)
- mypy in CI for type checking (Codacy does not run mypy; add it as a separate CI step)
- Codacy in CI for integrated quality analysis, security scanning, coverage tracking, and PR -weight: 500;">status checks
- Semgrep optionally for advanced security rules, especially for Django/Flask teams that need cross-file taint analysis - Will AI Replace Code Reviewers? What the Data Actually Shows
- Best AI Code Review Tools in 2026 - Expert Picks
- Best AI Code Review Tools for Pull Requests in 2026
- 7 Best CodeRabbit Alternatives for AI Code Review in 2026
- CodeRabbit Pricing in 2026: Free Tier, Pro Plans, and Enterprise Costs