Skip to content

Fix pyright type errors and add pyright pre-commit hook #125

Description

@ironcladgeek

Overview

Running uv run pyright . reveals 415 type errors across the codebase. These should be fixed to improve type safety, IDE support, and prevent runtime errors. After fixing, pyright should be added as a pre-commit hook to prevent regressions.

Error Summary

415 errors, 0 warnings, 0 informations

Error Categories

1. None/Optional Parameter Type Errors (~80 errors)

Pattern: Expression of type "None" cannot be assigned to parameter

Files affected:

  • src/agents/base/interface.py (lines 15, 43)
  • src/agents/llm/factory.py (lines 46, 97, 127)
  • src/agents/llm/hybrid.py (lines 43, 202)
  • src/agents/rule_based/sentiment.py (lines 17, 35)
  • src/agents/rule_based/synthesis.py (lines 15, 32)
  • src/analysis/fundamental.py (lines 32-34, 152-155)

Example:

# src/agents/base/interface.py:15
def __init__(self, tools: list = None):  # ❌ None not assignable to list
    # Should be:
def __init__(self, tools: Optional[list] = None):  # ✅
    self.tools = tools or []

Fix: Add proper Optional[] type hints and handle None cases


2. Missing Pydantic Model Parameters (~120 errors)

Pattern: No parameter named "rsi", Arguments missing for parameters

Files affected:

  • src/analysis/metadata_extractor.py (lines 42-50)
  • src/analysis/normalizer.py (lines 499-507, 1201, 1231)
  • tests/unit/website/test_generator_comprehensive.py (lines 36-50)

Example:

# Attempting to create TechnicalMetrics without required fields
TechnicalMetrics(
    rsi=70.5,           # ❌ Not a parameter
    macd=0.5,           # ❌ Not a parameter  
    sma_20=150.0        # ❌ Not a parameter
)

# TechnicalMetrics likely defined as:
class TechnicalMetrics(BaseModel):
    # Missing field definitions
    pass

Root cause: Pydantic models missing field definitions

Fix: Add all required fields to Pydantic models or use model_construct() for dynamic creation


3. Type Narrowing Issues (~100 errors)

Pattern: Cannot access attribute "X" for class "dict[str, Any]"

Files affected:

  • src/analysis/normalizer.py (lines 111-138, 432-665)
  • src/agents/llm/hybrid.py (line 87)

Example:

# normalizer.py:111
synth_result: dict[str, Any] = get_synthesis()
risk_level = synth_result.risk_level  # ❌ .risk_level not defined on dict

# Fix: Type narrowing or Pydantic validation
synth_result: SignalSynthesisOutput = get_synthesis()
risk_level = synth_result.risk_level  # ✅

Fix: Replace dict[str, Any] with proper Pydantic models or add type narrowing


4. pandas-ta Type Issues (~50 errors)

Pattern: Argument of type "Series | DataFrame" cannot be assigned to parameter "close" of type "Series"

Files affected:

  • src/analysis/technical_indicators.py (lines 193-280)

Example:

# technical_indicators.py:193
close_series = df['close']  # Type: Series | DataFrame (union type)
rsi_value = ta.rsi(close_series, length=14)  # ❌ ta.rsi expects Series only

# Fix: Type assertion
close_series = df['close']
assert isinstance(close_series, pd.Series)
rsi_value = ta.rsi(close_series, length=14)  # ✅

Fix: Add type assertions after DataFrame column access


5. Possibly Unbound Variables (~10 errors)

Pattern: "ta" is possibly unbound

Files affected:

  • src/analysis/technical_indicators.py (lines 62-280)

Example:

try:
    import pandas_ta as ta
except ImportError:
    logger.error("pandas_ta not available")
    # ta not defined here

result = ta.rsi(...)  # ❌ ta possibly unbound

Fix: Define fallback or raise ImportError immediately


6. String vs Enum Type Errors (~20 errors)

Pattern: "str" is not assignable to "Recommendation"

Files affected:

  • src/analysis/signal_creator.py (line 109)

Example:

# signal_creator.py:109
recommendation: str = "buy"
signal = InvestmentSignal(recommendation=recommendation)  # ❌

# Fix: Use enum
from src.analysis.models import Recommendation
recommendation = Recommendation.BUY  # ✅

Fix: Use proper enum types instead of strings


7. Missing Private Method Definitions (~15 errors)

Pattern: Cannot access attribute "_extract_from_technical_pydantic"

Files affected:

  • src/analysis/normalizer.py (lines 432, 434, 438, 531, 533, etc.)

Example:

# normalizer.py:432
result = AnalysisResultNormalizer._extract_from_technical_pydantic(data)
# ❌ Method doesn't exist or isn't marked as @staticmethod/@classmethod

Fix: Define missing methods or fix method visibility


8. Test Fixture Issues (~20 errors)

Pattern: Cannot assign to attribute, None is not assignable

Files affected:

  • tests/unit/website/test_generator_comprehensive.py (lines 730, 743)

Example:

signal.risk = None  # ❌ risk expects RiskAssessment, not None
signal.scores = None  # ❌ scores expects ComponentScores, not None

Fix: Use proper mock objects or make fields Optional


Implementation Plan

Phase 1: Low-Hanging Fruit (Est: 2-4 hours)

  • Fix None/Optional type hints (~80 fixes)
  • Add type assertions for pandas Series (~50 fixes)
  • Fix unbound variable issues (~10 fixes)

Phase 2: Pydantic Model Fixes (Est: 4-6 hours)

  • Add missing fields to TechnicalMetrics
  • Add missing fields to FundamentalMetrics
  • Add missing fields to SentimentMetrics
  • Update all model construction sites

Phase 3: Type Narrowing (Est: 6-8 hours)

  • Replace dict[str, Any] with Pydantic models in normalizer
  • Add proper type guards and isinstance checks
  • Fix attribute access patterns

Phase 4: Enum and Edge Cases (Est: 2-3 hours)

  • Convert string literals to proper enums
  • Fix private method definitions
  • Fix test fixtures

Phase 5: Validation (Est: 1 hour)

  • Run uv run pyright . - should show 0 errors
  • Run full test suite - all tests passing
  • Add pyright to pre-commit hooks

Add Pyright to Pre-Commit

After all errors are fixed, add to .pre-commit-config.yaml:

  - repo: local
    hooks:
      # ... existing hooks ...
      
      - id: pyright
        name: pyright
        entry: uv run pyright
        language: system
        types: [python]
        pass_filenames: false
        stages: [commit]

Note: Only add after ALL errors are fixed (0 errors), otherwise pre-commit will block all commits.


Benefits

Better IDE support - VSCode/PyCharm will provide accurate autocomplete
Catch bugs early - Type errors caught before runtime
Improved refactoring - Safer code changes with type checking
Better documentation - Types serve as inline documentation
Prevent regressions - Pre-commit hook ensures type safety maintained


Success Criteria

  • uv run pyright . shows 0 errors
  • All 820+ tests still passing
  • Pyright added to .pre-commit-config.yaml
  • CI pipeline includes pyright check
  • Documentation updated with type checking guidance

Related Issues

Priority

MEDIUM-HIGH - Improves code quality and developer experience, but doesn't block functionality

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions