Skip to content

Repository files navigation

Code Quality Analyzer (PyExamine)

CIPyPI versionPython versionsLicense: MIT

A comprehensive Python static analysis tool that detects code smells, architectural smells, and structural smells in Python projects. Helps developers identify maintainability issues and technical debt before they compound.


Table of Contents


Features

Code Smells — 18 detectors + 5 cross-file detectors

SmellWhat it flags
Long MethodMethods exceeding a line count threshold
Large ClassClasses with too many methods
Primitive ObsessionFunctions overusing primitive types instead of objects
Long Parameter ListFunctions with too many parameters
Switch StatementsLong if/elif chains that should be polymorphism
Divergent ChangeClasses that change for many different reasons
Shotgun SurgeryMethods called across too many unrelated contexts
Excessive CommentsFiles where comment density is unusually high
Lazy ClassClasses too small to justify their existence
Feature EnvyMethods that access another object's data more than their own
Message ChainsLong chains of method calls (Law of Demeter violations)
Middle ManClasses that mostly delegate to another class
Speculative GeneralityAbstract classes or unused hooks added "just in case"
Temporary FieldInstance fields set only in some code paths
Dead CodeFunctions defined but never called
Unused ParametersMethod signatures more complex than the implementation needs
Data ClassClasses with only fields and no real behaviour
Large Comment BlocksOversized comment blocks suggesting code that needs refactoring
(cross-file) Data ClumpsParameter groups that appear together across many functions
(cross-file) Duplicate CodeIdentical or near-identical code blocks across files
(cross-file) Inappropriate IntimacyPairs of classes that share too many internals
(cross-file) Alternative ClassesClasses with identical public interfaces but different names
(cross-file) Parallel InheritanceParallel class hierarchies that change together

Architectural Smells — 8 detectors

SmellWhat it flags
God ObjectModules with an excessive number of public functions
Cyclic DependencyCircular import chains between modules
Hub-like DependencyModules that are overly central (high fan-in + fan-out)
Scattered FunctionalityThe same function name repeated across many modules
Unstable DependencyModules that depend far more on others than others depend on them
Orphan ModuleModules with no connections to the rest of the project
Improper API UsageModules that repeat the same API call patterns excessively
Redundant AbstractionsModule pairs with nearly identical public function sets

Structural Smells — 12 metric-based detectors

SmellMetricWhat it flags
Too Many MethodsNOMClasses with more methods than the threshold
Low CohesionLCOMClasses whose methods don't share fields
High ResponseRFCClasses that trigger too many responses to a message
Too Many Classes in ModuleNOCCModules with an excessive number of classes
Deep InheritanceDITClasses with deep inheritance chains
Large ModuleLOCModules exceeding a line count
Too Many Classes in ProjectNOCProjects with an excessive class count
High Cyclomatic ComplexityCCMethods with too many decision branches
High Fan-outFan-outModules depending on too many others
High Fan-inFan-inModules depended upon by too many others
Large FileFile lengthFiles exceeding a line count
Too Many BranchesBranchesMethods with too many conditional branches

Use Cases

Auditing a legacy codebase

Before refactoring a large project, get a full picture of its technical debt:

analyze_code_quality /path/to/legacy_project \
--output reports/initial_audit \
--ignore venv .tox build dist

Open reports/initial_audit.csv in Excel or a BI tool to sort smells by severity and prioritize which modules to tackle first.

Enforcing quality gates in CI

Fail a pull request if new smells are introduced. Add the tool to your CI pipeline (see GitHub Actions and GitLab CI below) and compare reports between the base and head commits.

Focused structural review

When reviewing a new service or library for object-oriented design issues, run only the structural detector to get OO metric violations (LCOM, CBO, DIT, cyclomatic complexity) without noise from the other categories:

analyze_code_quality src/my_service --type structural --output oo_review

Tracking technical debt over time

Run the tool on every merge to main, save the CSV to a time-series store or artifact, and chart smell counts per category over sprints to measure whether debt is being paid down.

Pre-merge code review assistance

Developers run the tool locally before opening a PR:

analyze_code_quality . --ignore tests docs venv --type code

This surfaces code smells (long methods, large classes, dead code, etc.) in the diff before reviewers see it.

Onboarding onto an unfamiliar codebase

Run all three detectors and read the architectural smells report first — cyclic dependencies, god objects, and hub-like modules give a high-level map of the system's problem areas before diving into individual files.


Requirements

  • Python 3.10 or higher
  • uv (recommended) or pip

Installation

From PyPI with uv (recommended)

Install uv first if you haven't:

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Then install the tool globally:

uv tool install code-quality-analyzer

The analyze_code_quality command is now available everywhere. Make sure ~/.local/bin (Linux/macOS) or %USERPROFILE%\.local\bin (Windows) is on your PATH.

From PyPI with pip

pip install code-quality-analyzer

From source with uv

git clone https://github.com/KarthikShivasankar/python_smells_detector
cd python_smells_detector
uv sync

Run the tool without installing it globally:

uv run analyze_code_quality /path/to/project

From source with pip

git clone https://github.com/KarthikShivasankar/python_smells_detector
cd python_smells_detector
pip install -e .
analyze_code_quality /path/to/project

Usage

Basic analysis

Analyze all three smell categories in a project:

analyze_code_quality /path/to/project

Analyze a specific category

analyze_code_quality /path/to/project --type code
analyze_code_quality /path/to/project --type architectural
analyze_code_quality /path/to/project --type structural

Ignore folders

Skip directories you don't want analyzed — useful for vendored code, test fixtures, virtual environments, or build output:

analyze_code_quality /path/to/project --ignore tests docs venv build

Multiple names are space-separated. The names are matched against directory basenames (not full paths), so --ignore tests skips any folder named tests anywhere in the tree.

Save a report

By default the report is printed to stdout. Use --output to write files instead:

# Creates report.txt (human-readable) and report.csv (structured)
analyze_code_quality /path/to/project --output report

The .txt and .csv suffixes are added automatically — just provide the base name.

Full example

analyze_code_quality /path/to/project \
--type code \
--config my_thresholds.yaml \
--output results/analysis \
--ignore tests docs venv __pycache__ \
--debug

CLI reference

ArgumentTypeDescriptionDefault
directorypositionalPath to the project directory to analyze(required)
--typeoptionLimit analysis to code, architectural, or structuralall three
--configoptionPath to a YAML threshold configuration file. If omitted and no code_quality_config.yaml exists in the current directory, the config bundled with the package is used.code_quality_config.yaml
--outputoptionBase name for output files — generates <name>.txt + <name>.csvprint to stdout
--ignoreoption (repeatable)Directory names to skip during traversalnone
--debugflagEnable verbose debug logging to console and code_analysis.logoff

Ways to Run

As a CLI command

The primary interface. Works after pip install code-quality-analyzer or uv tool install code-quality-analyzer:

analyze_code_quality /path/to/project
analyze_code_quality /path/to/project --type structural --output report

As a Python module

If the entry point is not on your PATH (e.g., after uv sync from source), invoke the module directly:

python -m code_quality_analyzer.main /path/to/project
# or via uv
uv run python -m code_quality_analyzer.main /path/to/project

Python API

Use the detectors programmatically inside your own scripts or tooling:

fromcode_quality_analyzer.config_handlerimportConfigHandlerfromcode_quality_analyzer.code_smell_detectorimportCodeSmellDetectorfromcode_quality_analyzer.structural_smell_detectorimportStructuralSmellDetectorfromcode_quality_analyzer.architectural_smell_detectorimportArchitecturalSmellDetectorfromcode_quality_analyzer.mainimport (
analyze_code_smells,
analyze_structural_smells,
analyze_architectural_smells,
generate_report,
)
config=ConfigHandler("code_quality_config.yaml")
# Run only code smell detectioncode_detector=CodeSmellDetector(config.get_thresholds("code_smells"))
code_smells=analyze_code_smells("src/", code_detector, ignore_dirs={"tests", "venv"})
# Run structural detectionstruct_detector=StructuralSmellDetector(config.get_thresholds("structural_smells"))
structural_smells=analyze_structural_smells("src/", struct_detector)
# Run architectural detectionarch_detector=ArchitecturalSmellDetector(config.get_thresholds("architectural_smells"))
architectural_smells=analyze_architectural_smells("src/", arch_detector)
# Generate text + CSV reportsgenerate_report(code_smells, architectural_smells, structural_smells,
output_txt="report.txt", output_csv="report.csv")
# Or just inspect the results in codeforsmellincode_smells:
print(smell.name, smell.file_path, smell.severity)

Each smell object is a dataclass with fields: name, description, file_path, module_class, line_number, severity.

Pre-commit hook

Run the analyzer automatically before every commit. Add this to .pre-commit-config.yaml:

repos:
- repo: localhooks:
- id: code-quality-analyzername: Code Quality Analyzerentry: analyze_code_qualityargs: ['.', '--type', 'code', '--ignore', 'tests', 'venv', 'build']language: systempass_filenames: false

Install the hooks:

pip install pre-commit
pre-commit install

GitHub Actions

Add this workflow to .github/workflows/code-quality.yml to analyze every pull request:

name: Code Quality Analysison:
pull_request:
branches: [main, dev]push:
branches: [main]jobs:
analyze:
runs-on: ubuntu-lateststeps:
- uses: actions/checkout@v4
- name: Set up Pythonuses: actions/setup-python@v5with:
python-version: "3.11"
- name: Install uvrun: pip install uv
- name: Install code-quality-analyzerrun: uv tool install code-quality-analyzer
- name: Run analysisrun: | analyze_code_quality src/ \ --output reports/quality \ --ignore tests venv build dist \ --type code - name: Upload reportuses: actions/upload-artifact@v4with:
name: code-quality-reportpath: reports/

GitLab CI

Add to .gitlab-ci.yml:

code-quality:
stage: testimage: python:3.11-slimbefore_script:
- pip install uv
- uv tool install code-quality-analyzerscript:
- analyze_code_quality src/--output reports/quality--ignore tests venv buildartifacts:
paths:
- reports/expire_in: 7 daysrules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"

Configuration

All detection thresholds are defined in code_quality_config.yaml. The tool looks for this file in the current working directory by default, and if it isn't found there it falls back to the configuration bundled with the installed package — so a fresh pip install code-quality-analyzer works out of the box with no config file present. Use --config to point to a different file.

Copy the file and edit the value fields to tune sensitivity for your project:

cp code_quality_config.yaml my_project_thresholds.yaml
# edit my_project_thresholds.yaml
analyze_code_quality /path/to/project --config my_project_thresholds.yaml

Code smell thresholds

KeyDefaultDescription
LONG_METHOD_LINES45Methods longer than this many lines are flagged
LARGE_CLASS_METHODS15Classes with more than this many methods are flagged
PRIMITIVE_OBSESSION_COUNT4Functions with more than this many primitive parameters are flagged
LONG_PARAMETER_LIST5Functions with more parameters than this are flagged
COMPLEX_CONDITIONAL3if/elif chains longer than this trigger a switch-statement smell
DIVERGENT_CHANGE_PREFIXES4Classes with more distinct method-name prefixes than this are flagged
DIVERGENT_CHANGE_METHODS5Classes with more methods changing for different reasons than this are flagged
SHOTGUN_SURGERY_CALLS5Methods called in more places than this are flagged
SHOTGUN_SURGERY_CONTEXTS3Methods called in more different contexts than this are flagged
EXCESSIVE_COMMENTS_RATIO0.3Files where comment lines exceed this fraction of total lines are flagged
LARGE_COMMENT_BLOCKS5Consecutive comment blocks larger than this are flagged
LAZY_CLASS_METHODS4Classes with this few methods or fewer are flagged as lazy
FEATURE_ENVY_CALLS3Methods making more calls to another object than this are flagged
INAPPROPRIATE_INTIMACY_SHARED3Class pairs sharing more internals than this are flagged
MESSAGE_CHAIN_LENGTH3Call chains longer than this are flagged
MIDDLE_MAN_RATIO0.5Classes where more than this fraction of methods only delegate are flagged
DATA_CLUMPS_THRESHOLD6Parameter groups appearing together more than this many times are flagged
TEMPORARY_FIELD_THRESHOLD3Fields used in fewer methods than this are flagged as temporary
ALTERNATIVE_CLASSES_THRESHOLD3Classes with more shared methods than this are checked for identical interfaces
DUPLICATE_CODE_THRESHOLD15Functions with more shared lines than this are flagged as duplicates
DUPLICATE_CODE_MIN_LINES5Minimum block length (lines) to consider when detecting duplicate code
DEAD_CODE_THRESHOLD3Functions not called more than this many times are flagged as dead code
SPECULATIVE_GENERALITY_THRESHOLD4Abstract classes with fewer concrete subclasses than this are flagged
UNUSED_PARAMETERS_THRESHOLD6Accumulation of unused parameters across methods above this is flagged

Architectural smell thresholds

KeyDefaultDescription
GOD_OBJECT_FUNCTIONS20Modules with more public functions than this are flagged as god objects
UNSTABLE_DEPENDENCY_THRESHOLD0.8Modules with instability (out / total) above this ratio are flagged
HUB_LIKE_DEPENDENCY_RATIO0.3Modules connected to more than this fraction of all modules are flagged as hubs
REDUNDANT_ABSTRACTION_SIMILARITY0.7Module pairs sharing more than this fraction of functions are flagged as redundant
IMPROPER_API_USAGE_RATIO0.7Modules where repetitive API calls exceed this fraction of total calls are flagged
CYCLIC_DEPENDENCY_MAX_LENGTH3Cyclic dependency chains longer than this are flagged

Structural smell thresholds

KeyDefaultMetricDescription
NOM_THRESHOLD10NOMClasses with more methods than this are flagged
WMPC1_THRESHOLD20WMPC1Weighted Methods per Class (complexity-based) above this
WMPC2_THRESHOLD20WMPC2Weighted Methods per Class (parameter-based) above this
SIZE2_THRESHOLD15SIZE2Classes with more total members (methods + fields) than this
WAC_THRESHOLD10WACClasses with more fields than this are flagged
LCOM_THRESHOLD10LCOMLack of Cohesion in Methods above this threshold
RFC_THRESHOLD20RFCResponse for a Class above this threshold
NOCC_THRESHOLD10NOCCModules with more classes than this are flagged
DIT_THRESHOLD3DITClasses with inheritance depth greater than this are flagged
LOC_THRESHOLD150LOCModules with more lines than this are flagged
MPC_THRESHOLD25MPCMessage Passing Coupling above this threshold
CBO_THRESHOLD5CBOCoupling Between Object classes above this threshold
NOC_THRESHOLD7NOCProjects with more distinct classes than this are flagged
CYCLOMATIC_COMPLEXITY_THRESHOLD10CCMethods with cyclomatic complexity above this are flagged
MAX_FANOUT15Fan-outModules depending on more than this many others are flagged
MAX_FANIN15Fan-inModules depended upon by more than this many others are flagged
MAX_FILE_LENGTH250Files with more lines than this are flagged
MAX_BRANCHES10Methods with more conditional branches than this are flagged
MAX_NESTING_DEPTH4Code nested deeper than this is flagged

Output formats

The tool produces three output types:

FormatHow to get itContents
ConsoleDefault (no --output)Human-readable report printed to stdout
Text file (*.txt)--output <name>Same human-readable report, saved to <name>.txt
CSV file (*.csv)--output <name>One row per smell: Type, Name, Description, File, Module/Class, Line Number, Severity
Log file (code_analysis.log)Always writtenDetailed analysis trace, warnings, and parse errors

The CSV is useful for importing into spreadsheets, dashboards, or CI tools for trend tracking.


Example output

Code Quality Analysis Report
============================
Structural Smells:
-------------------
- High Cyclomatic Complexity: Method 'process_data' has complexity of 15
Line: 45
File: src/processor.py
Severity: high
- Large Module (LOC): Module 'data_manager' has 320 lines
File: src/data_manager.py
Severity: medium
Code Smells:
------------
- Large Class: 'DataManager' has 22 non-trivial methods in src/data_manager.py at line 10
- Long Method: 'DataManager.transform' has 68 lines in src/data_manager.py at line 145
- Feature Envy: Method 'calculate_metrics' makes 8 calls to 'stats' but only 1 local call
- Data Clumps: Parameters user_id, user_name, user_email appear together in 7 functions
Architectural Smells:
---------------------
- Cyclic Dependency: Strong cyclic dependency detected: loader -> parser -> loader
Cycle strength: 3 mutual dependencies
- God Object: Module 'utils' has too many public functions (27)
Summary:
--------
Total Structural Smells: 2
Total Code Smells: 4
Total Architectural Smells: 2

Development

Setup

git clone https://github.com/KarthikShivasankar/python_smells_detector
cd python_smells_detector
# Install runtime + dev dependencies (pytest, sphinx, etc.)
uv sync --extra dev

Running tests

The test suite covers all three detectors (code, structural, architectural), the config handler, and the main orchestration layer. Tests write real Python source files into temporary directories — no mocking.

# Install dev dependencies first (includes pytest)
uv sync --extra dev
# Run the full test suite (123 tests)
uv run python -m pytest tests/
# Run a specific test file
uv run python -m pytest tests/test_code_smell_detector.py
uv run python -m pytest tests/test_structural_smell_detector.py
uv run python -m pytest tests/test_architectural_smell_detector.py
uv run python -m pytest tests/test_config_handler.py
uv run python -m pytest tests/test_main.py
# Run a single test class
uv run python -m pytest tests/test_code_smell_detector.py::TestLargeClass
uv run python -m pytest tests/test_structural_smell_detector.py::TestLOC
# Run a single test by name
uv run python -m pytest tests/test_code_smell_detector.py::TestLongMethod::test_detects_method_over_threshold
# Run with verbose output
uv run python -m pytest tests/ -v
# Run with coverage report
uv run python -m pytest tests/ --cov=src/code_quality_analyzer --cov-report=term-missing
# Verify all 123 tests pass (exit 0 = success)
uv run python -m pytest tests/ -q &&echo"All tests passed"

Test structure

FileWhat it covers
tests/test_code_smell_detector.py18 per-file detectors + cross-file smells (data clumps, duplicate code, alternative classes, parallel inheritance). Positive and negative cases for each.
tests/test_structural_smell_detector.py12 metric-based detectors: NOM, LOC, NOC, NOCC, DIT, LCOM, RFC, WAC, SIZE2, cyclomatic complexity, fan-in/out, branches, file length.
tests/test_architectural_smell_detector.pyAll 8 architectural smell detectors: God Object, Scattered Functionality, Redundant Abstraction, Improper API Usage, Orphan Module, Cyclic Dependency, Unstable Dependency, Hub-like Dependency.
tests/test_config_handler.pyConfig file loading, threshold validation, unknown-key handling, malformed YAML error handling.
tests/test_main.pyCLI parser flags, analyze_* functions, generate_report, generate_csv_report (field names, row contents, empty input).

Note:pytest is blocked by an application control policy on some Windows setups — use python -m pytest instead of the bare pytest command.

Linting

Linting is handled by ruff (configured in pyproject.toml):

uv run ruff check src/ tests/
uv run ruff check src/ tests/ --fix # auto-fix

Ruff is pinned to target-version = "py310", which guards against accidentally introducing Python 3.12-only f-string syntax (line breaks inside {...} replacement fields) that would break the package on Python 3.10/3.11.

Verifying across Python versions

The package supports Python 3.10–3.13. To run the suite against a specific interpreter without touching your project venv:

uv run --no-project --python 3.10 --with . --with pytest python -m pytest tests/

Updating the documentation

Documentation lives in docs/source/ as reStructuredText and is built with Sphinx (config in docs/source/conf.py). The API reference is generated from docstrings via autodoc, so updating a docstring updates the docs.

# Build the HTML docs (sphinx ships in the dev extra). Expect zero warnings.
uv run --extra dev sphinx-build docs/source docs/build/html
# Windows legacy scriptcd docs && make.bat html

What to update when you change docs:

  1. Edit the relevant docs/source/*.rst (or the docstring being rendered).
  2. If you add a new page, add it to the toctree in docs/source/index.rst.
  3. Bump release in docs/source/conf.py when the version changes.
  4. Rebuild with the command above and confirm no warnings.
  5. Commit both the docs/source/ change and the regenerated docs/build/html/ (the built HTML is tracked in this repo).

Read the Docs:.readthedocs.yaml drives the hosted docs build, which happens automatically from docs/source/ on push — you do not need to commit docs/build/ for Read the Docs, only for the in-repo copy.

Continuous integration (CI/CD)

Workflows live in .github/workflows/:

WorkflowFileTriggerWhat it does
CIci.ymlpush / PR to main/devLints with ruff, runs the test suite on Python 3.10–3.13, audits dependencies (pip-audit), then builds and twine checks the distribution.
CodeQLcodeql.ymlpush / PR + weeklyGitHub static security analysis of the Python source.
Publishpublish.ymlGitHub Release publishedBuilds, validates, and uploads to PyPI via Trusted Publishing (OIDC).

Dependabot (.github/dependabot.yml) opens weekly PRs for outdated Python deps and GitHub Actions. .github/CODEOWNERS auto-requests review from the maintainer.

A change is "green" when CI passes on all four Python versions. Run the same checks locally before pushing:

uv run ruff check src/ tests/
uv run python -m pytest tests/
uv build && uv run --with twine python -m twine check dist/*

Maintaining & releasing the package

The package is code-quality-analyzer on PyPI; the import name is code_quality_analyzer. Releases are automated through Trusted Publishing — no API token is stored anywhere.

One-time setup — add a trusted publisher at PyPI → Project → Settings → Publishing (docs):

FieldValue
OwnerKarthikShivasankar
Repository namepython_smells_detector
Workflow namepublish.yml
Environment namepypi

Release checklist:

  1. Make sure main is green (CI passing).
  2. Bump the version in bothpyproject.toml (version) and docs/source/conf.py (release). This project uses semantic versioning.
  3. Rebuild the docs (uv run --extra dev sphinx-build docs/source docs/build/html).
  4. Commit and push: git commit -am "chore: bump version to X.Y.Z" && git push.
  5. Tag and publish a GitHub Release — this triggers publish.yml:
    git tag vX.Y.Z
    git push origin vX.Y.Z
    gh release create vX.Y.Z --title "vX.Y.Z" --notes "..."
  6. Watch the Publish workflow; confirm the new version appears at https://pypi.org/project/code-quality-analyzer/.

Note: PyPI versions are immutable — you cannot re-upload an existing version. If a release is broken, bump to the next patch version and yank the bad one in the PyPI UI (Project → Releases → Options → Yank). Yanking hides it from new installs without breaking existing pins.

Manual publish (fallback, if Trusted Publishing isn't set up):

uv build
uv run --with twine python -m twine check dist/*
uv publish --token pypi-xxxxxxxx # or set UV_PUBLISH_TOKEN

Troubleshooting

analyze_code_quality: command not found

  • Installed via uv tool install → make sure ~/.local/bin (Linux/macOS) or %USERPROFILE%\.local\bin (Windows) is on your PATH
  • Installed via uv sync (source) → use uv run analyze_code_quality ... instead of invoking the script directly
  • Installed via pip install -e . → make sure the pip scripts directory is on your PATH

Parse errors / files being skipped

  • Ensure files being analyzed are valid Python 3 syntax
  • Files with syntax errors are skipped and logged — check code_analysis.log for details
  • The analysis continues for all other files

KeyError or missing threshold

  • Your config file is missing a required key — use the bundled code_quality_config.yaml as a base and add missing keys

Analysis is slow on large projects

  • Use --type code / --type architectural / --type structural to run one category at a time
  • Use --ignore to skip large directories that don't need analysis (e.g., venv, node_modules, build)
  • The networkx graph construction in the architectural and structural detectors is the main performance cost on large trees

No smells detected

  • The thresholds in your config may be too high for the project being analyzed
  • Try lowering key values in code_quality_config.yaml and re-running
  • Use --debug to see per-file analysis trace

Architecture

src/code_quality_analyzer/
├── main.py # CLI entry point, orchestration, report generation
├── code_smell_detector.py # CodeSmellDetector — astroid-based, 18+5 detectors
├── structural_smell_detector.py # StructuralSmellDetector — ast + networkx, OO metrics
├── architectural_smell_detector.py # ArchitecturalSmellDetector — ast + networkx, module graphs
├── config_handler.py # Loads and validates code_quality_config.yaml
└── exceptions.py # CodeAnalysisError with file/line/function context
ModuleDetector ClassParserScope
code_smell_detector.pyCodeSmellDetectorastroidPer-file + cross-file
structural_smell_detector.pyStructuralSmellDetectorstdlib ast + networkxDirectory
architectural_smell_detector.pyArchitecturalSmellDetectorstdlib ast + networkxDirectory

Two-phase API for code smells:detect_smells(file_path) is called once per file (accumulates internal state), then detect_cross_file_smells() is called once after all files to report smells requiring multi-file context (data clumps, duplicate code, inappropriate intimacy, alternative classes, parallel inheritance).

Configuration flow:ConfigHandler reads code_quality_config.yaml and hands a threshold dict to each detector constructor. Thresholds can be tuned without touching code.

Error handling:CodeAnalysisError (in exceptions.py) carries file_path, line_number, and function_name. Parse errors are caught per-file and logged to code_analysis.log without aborting the run.


Contributing

Contributions are welcome. Please open an issue before submitting large changes.

  1. Fork the repository and create a feature branch
  2. Set up the dev environment: uv sync --extra dev
  3. Make your changes and add or update tests
  4. uv run python -m pytest tests/ — all tests must pass
  5. uv run ruff check src/ tests/ — no lint errors
  6. Open a pull request with a clear description

License

MIT License — see LICENSE for details.


Citation

If you use PyExamine in academic work, please cite the following paper:

@inproceedings{shivashankar2025pyexamine,
title = {PyExamine: A Comprehensive, Un-Opinionated Smell Detection Tool for Python},
author = {Shivashankar, Karthik and Martini, Antonio},
booktitle = {2025 IEEE/ACM 22nd International Conference on Mining Software Repositories (MSR)},
pages = {763--774},
year = {2025},
publisher = {IEEE}
}

Shivashankar, K., & Martini, A. (2025, April). PyExamine: A Comprehensive, Un-Opinionated Smell Detection Tool for Python. In 2025 IEEE/ACM 22nd International Conference on Mining Software Repositories (MSR) (pp. 763–774). IEEE.


Acknowledgments

  • Code smell taxonomy from Martin Fowler's Refactoring: Improving the Design of Existing Code
  • Structural metrics (CBO, LCOM, DIT, RFC, NOM) from standard object-oriented quality literature
  • AST parsing powered by astroid and Python's stdlib ast module
  • Dependency graph analysis powered by networkx
  • Progress reporting powered by tqdm

About

PyExamine: A Comprehensive, Un-Opinionated Smell Detection Tool for Python

Resources

Security policy

Stars

28 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages