Skip to content

Conversation

@kyamagu
Copy link
Contributor

@kyamagu kyamagu commented Nov 7, 2025

Summary

This PR refactors the psd_tools.api package to eliminate circular dependency issues by introducing typing.Protocol classes. The refactoring improves type safety while maintaining backward compatibility.

Changes

  • New file: src/psd_tools/api/protocols.py - Defines LayerProtocol, GroupMixinProtocol, and PSDProtocol interfaces
  • Updated modules: Replaced Any type hints with proper Protocol references in:
    • mask.py - Uses LayerProtocol instead of Any
    • effects.py - Uses LayerProtocol instead of Any
    • smart_object.py - Uses LayerProtocol with proper type hints
    • pil_io.py - Uses Protocol forward references with TYPE_CHECKING
    • numpy_io.py - Uses Protocol forward references with TYPE_CHECKING
    • layers.py - Uses PSDImage forward references with TYPE_CHECKING
    • psd_image.py - Uses Layer forward references with TYPE_CHECKING
  • Code quality improvements:
    • Removed Python 2 compatibility code (explicit object inheritance)
    • Removed unused imports
    • Fixed code formatting
    • Sorted imports

Technical Approach

  1. Protocol Pattern: Created structural type interfaces that define contracts without inheritance
  2. TYPE_CHECKING: Used conditional imports that only execute during static type checking, not at runtime
  3. Forward References: Used string-based type annotations to avoid circular imports at runtime
  4. Lenient Protocols: Made Protocol attributes use Any where needed to accommodate implementation details (TypeVars, etc.)

Testing

  • ✅ All 143 API tests passing
  • ✅ MyPy errors reduced from 163 to 94 (below original baseline of 98)
  • ✅ All ruff linting checks passing
  • ✅ No runtime behavior changes

Impact

  • Better Type Safety: Eliminates Any types in favor of explicit Protocol interfaces
  • No Circular Dependencies: Modules can be imported in any order without issues
  • Backward Compatible: No breaking changes to public API
  • Maintainability: Clearer interfaces make it easier to understand component relationships

🤖 Generated with Claude Code

kyamagu and others added 5 commits November 7, 2025 09:58
…ocols

This commit introduces Protocol-based type hints and TYPE_CHECKING guards
to eliminate circular import issues between psd_tools.api modules.

## Changes

### New File
- **protocols.py**: Created comprehensive Protocol definitions for Layer,
  GroupMixin, and PSDImage interfaces to enable proper type hints without
  circular dependencies

### Updated Modules
- **mask.py**: Use LayerProtocol instead of Any, removed circular import TODO
- **effects.py**: Use LayerProtocol instead of Any, removed circular import TODO
- **smart_object.py**: Use LayerProtocol instead of Any, removed circular import TODO
- **pil_io.py**: Add TYPE_CHECKING block with Protocol imports, replace all
  Any type hints with proper LayerProtocol/PSDProtocol forward references
- **numpy_io.py**: Add TYPE_CHECKING block with Protocol imports, replace
  all Any type hints with proper LayerProtocol/PSDProtocol forward references
- **layers.py**: Add TYPE_CHECKING block for PSDImage import, replace all
  Any type hints for psd parameters with "PSDImage" forward references,
  keep runtime imports in methods for isinstance checks
- **psd_image.py**: Add TYPE_CHECKING block for Layer import, remove
  redundant inline imports, use "Layer" forward reference in annotations

## Benefits

- **No circular dependencies**: TYPE_CHECKING pattern ensures imports only
  happen during static type checking, not at runtime
- **Better type safety**: Proper type hints throughout the API layer replace
  generic Any types
- **Improved IDE support**: Enables autocomplete and type checking in IDEs
- **Maintainable**: Protocol pattern provides clear interfaces without tight
  coupling
- **All tests pass**: 143 API tests pass with no failures

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
The LayerProtocol was missing the mask property that is accessed by
numpy_io.py and pil_io.py. This caused mypy errors when these modules
tried to access layer.mask on LayerProtocol types.

This fix reduces mypy errors from 163 to 99, eliminating 64 errors
and bringing us close to the baseline of 98 errors on main branch.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Updated LayerProtocol and related protocols to be more permissive:
- Changed _psd type from Optional[PSDProtocol] to Optional[Any] to allow
  both PSDImage and PSDProtocol without conflicts
- Changed parent property type to Optional[Any] to accommodate TypeVar
  usage in concrete Layer class (TGroupMixin)
- Updated composite() signatures to match actual implementations:
  - Added apply_icc parameter to LayerProtocol.composite()
  - Added np.ndarray types for color/alpha parameters
  - Made return type Optional[PILImage] for LayerProtocol
  - Added np.ndarray and None to color parameter in PSDProtocol

These changes allow concrete Layer and PSDImage classes to be used where
their Protocols are expected without type errors, while maintaining full
runtime compatibility.

Mypy error reduction: 99 → 94 errors (-5, now 4 below baseline)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Remove unused Self import from protocols.py
- Sort imports in adjustments.py (Curves, Levels alphabetically)
- Format function signatures in numpy_io.py and protocols.py

All ruff checks now pass. No functional changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
Remove unnecessary explicit inheritance from 'object' in class definitions.
This is a Python 2 compatibility pattern that is not needed in Python 3.9+
(the minimum supported version).

Changed classes:
- Layer: class Layer(object) -> class Layer
- Mask: class Mask(object) -> class Mask
- SmartObject: class SmartObject(object) -> class SmartObject

All tests pass. No functional changes.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@kyamagu kyamagu merged commit 353f0a9 into main Nov 7, 2025
7 checks passed
@kyamagu kyamagu deleted the refactor/eliminate-circular-dependencies branch November 7, 2025 01:27
@kyamagu kyamagu mentioned this pull request Nov 9, 2025
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants