A Rust CLI tool and library for finding all subclasses (direct and transitive) of a Python class within a codebase.
- Transitive subclass search: Finds all descendants, not just direct children
- Import resolution: Handles various import styles including relative imports
- Re-export support: Tracks classes defined in one module but exported from another
- Ambiguity detection: Detects when a class name appears in multiple modules and provides clear guidance
- Gitignore support: Automatically respects
.gitignorefiles using theignorecrate - Multiple output formats: Text, JSON, and Graphviz dot formats for visualization
- Fast and efficient: Written in Rust with parallel file traversal
- Smart caching: Caches parsed files with gzip compression for 2.5x speedup on repeated runs
- Configurable logging: Uses
env_loggerfor flexible logging control
brew install peter554/tap/pysubclassesgit clone <repository-url>
cd pysubclasses
cargo build --releaseThe binary will be available at target/release/pysubclasses.
To install globally:
cargo install --path .Find all subclasses of a class:
pysubclasses AnimalWhen the same class name appears in multiple modules, specify the module path:
pysubclasses Animal --module zoo.animalsThe --module flag also works with re-exporting modules. For example, if Node is defined in pkg._internal but re-exported through pkg/__init__.py, both of these will work:
pysubclasses Node --module pkg._internal
pysubclasses Node --module pkg # Shorter, using the re-exportSearch in a different directory than the current one:
pysubclasses Animal --directory /path/to/projectGet results in JSON format for scripting:
pysubclasses Animal --format jsonGenerate a graph visualization in Graphviz dot format:
# Output dot format
pysubclasses Animal --format dot
# Pipe to dot to generate an image
pysubclasses Animal --format dot | dot -Tpng > graph.png
pysubclasses Animal --format dot | dot -Tsvg > graph.svgControl whether to find only direct subclasses or all transitive subclasses:
# Find all transitive subclasses (default)
pysubclasses Animal --mode all
# Find only direct subclasses
pysubclasses Animal --mode directFor example, given:
class Animal: pass
class Mammal(Animal): pass
class Dog(Mammal): pass--mode all(default) would find: Mammal, Dog--mode directwould find: Mammal (only)
Control logging verbosity using the RUST_LOG environment variable:
# No logging (default) - only shows output
pysubclasses Animal
# Show info level logs
RUST_LOG=info pysubclasses Animal
# Show debug level logs
RUST_LOG=debug pysubclasses Animal
# Show only pysubclasses logs (filter out dependencies)
RUST_LOG=pysubclasses=debug pysubclasses AnimalExclude specific directories from analysis:
pysubclasses Animal --exclude ./testsForce re-parsing of all files:
pysubclasses Animal --no-cache- File Discovery: Walks the directory tree and finds all
.pyfiles, respecting.gitignore - Parsing: Parses each Python file using the ruff parser to extract class definitions and imports
- Registry Construction: Builds a registry of all classes with their base classes
- Import Resolution: Resolves base class references to actual class definitions using import statements
- Graph Building: Constructs an inheritance graph mapping parents to children
- Traversal: Performs a breadth-first search to find all transitive subclasses
- ✅ Simple inheritance:
class Dog(Animal) - ✅ Multiple inheritance:
class Dog(Animal, Pet) - ✅ Attribute-based inheritance:
class Dog(module.Animal) - ✅ Import statements:
import foo,from foo import Bar - ✅ Import aliases:
from foo import Bar as Baz - ✅ Relative imports:
from .module import Class - ✅ Re-exports via
__init__.py - ✅ Generic classes:
class Foo(Generic[T])
- ❌ Dynamic imports:
importlib.import_module()cannot be statically resolved - ❌ Star imports:
from foo import *requires parsing__all__ - ❌ Runtime-generated classes: Cannot detect classes created at runtime
- ❌ Forward references in strings:
class Foo("BaseClass")not fully supported