# Sprint 04: Constant Import Cleanup

| Field | Value |
|-------|-------|
| **Sprint Size** | S (Small) |
| **Owner** | TBD |
| **Prerequisites** | None |
| **Phase** | Deduplication Phase 3 |
| **Est. Pattern Reduction** | 30-40 patterns |
| **Status** | ✅ Complete |
| **Completed** | 2026-01-02 |

## Completion Summary

**Pattern Reduction**: 29 → 12 inline imports (17 patterns removed, 59% reduction)

### Files Updated
- `grpc/_mixins/project/_mixin.py`: 10 inline imports → module-level (2 constants)
- `grpc/_startup.py`: 3 inline imports → module-level (3 constants)
- `grpc/server.py`: 2 inline imports → module-level (1 constant)
- `application/services/export_service.py`: 2 inline imports → module-level (3 constants)

### Test Results
- 672 unit tests passed
- 514 integration tests passed
- 90 quality tests passed
- No circular import issues detected

## Open Issues

- [x] Determine if inline imports were intentional (lazy loading for circular deps) - verified safe to move
- [x] Verify no circular import issues after moving to module level - all imports verified
- [x] Check if any inline imports are in hot paths requiring lazy loading - none found

## Validation Status

| Component | Status | Notes |
|-----------|--------|-------|
| `noteflow.config.constants` | ✅ Exists | Centralized constants package |
| Module-level imports | ✅ Updated | High-frequency files consolidated |
| Circular dependency handling | ✅ Verified | No circular import issues |

## Objective

Move inline `from noteflow.config.constants import ...` statements from inside functions to module-level imports, reducing code duplication and improving import organization. This is a low-risk, high-frequency pattern cleanup.

## Key Decisions

| Decision | Rationale |
|----------|-----------|
| Move to module level only if safe | Avoid circular imports |
| Keep TYPE_CHECKING imports as-is | Already correct pattern |
| Group related constant imports | Improve readability |
| Test import order carefully | Ensure no runtime errors |

## What Already Exists

### Current Pattern Distribution

```
from_constants_import: 84 occurrences across 58 files
  - grpc/_mixins/project/_mixin.py: 20 (HIGHEST)
  - grpc/_startup.py: 3
  - grpc/service.py: 2
  - grpc/server.py: 2
  - application/services/export_service.py: 2
```

### Inline Import Pattern

**Current (inline):**
```python
async def CreateProject(self, request, context):
    from noteflow.config.constants import ERROR_WORKSPACE_ID_REQUIRED

    if not request.workspace_id:
        await abort_invalid_argument(context, ERROR_WORKSPACE_ID_REQUIRED)
```

**Target (module-level):**
```python
from noteflow.config.constants import ERROR_WORKSPACE_ID_REQUIRED

async def CreateProject(self, request, context):
    if not request.workspace_id:
        await abort_invalid_argument(context, ERROR_WORKSPACE_ID_REQUIRED)
```

## Scope

### Task 1: Audit Inline Imports (S)

Create a complete inventory of inline constant imports:

```bash
# Find all inline constant imports
grep -rn "from noteflow.config.constants import" src/noteflow/ --include="*.py" | \
  grep -v "^[^:]*:[0-9]*:from noteflow" | \
  sort -t: -k1,1 -k2,2n
```

**Output format:**
```
file:line:import_statement
```

### Task 2: Verify Circular Import Safety (S)

For each file with inline imports, verify module-level import is safe:

```python
# Test script: scripts/check_circular_imports.py
import sys
from pathlib import Path

def check_file(filepath: Path) -> bool:
    """Try importing the module to detect circular imports."""
    module_path = str(filepath.relative_to("src")).replace("/", ".").replace(".py", "")
    try:
        __import__(module_path)
        return True
    except ImportError as e:
        print(f"FAIL: {filepath} - {e}")
        return False

# Run on all files with inline imports
```

### Task 3: Update `project/_mixin.py` (M)

This file has 20 inline imports - the highest concentration:

**Current pattern (repeated 20 times):**
```python
async def CreateProject(self, ...):
    from noteflow.config.constants import ERROR_WORKSPACE_ID_REQUIRED
    ...

async def GetProject(self, ...):
    from noteflow.config.constants import ERROR_PROJECT_ID_REQUIRED
    ...

async def ListProjects(self, ...):
    from noteflow.config.constants import FEATURE_NAME_PROJECTS
    ...
```

**Consolidated module-level:**
```python
from noteflow.config.constants import (
    ERROR_PROJECT_ID_REQUIRED,
    ERROR_WORKSPACE_ID_REQUIRED,
    FEATURE_NAME_PROJECTS,
)

# All methods now use module-level constants
```

### Task 4: Update Remaining High-Frequency Files (S)

Update other files with 2+ inline imports:

| File | Inline Imports | Action |
|------|---------------|--------|
| `grpc/_startup.py` | 3 | Move to module level |
| `grpc/service.py` | 2 | Move to module level |
| `grpc/server.py` | 2 | Move to module level |
| `application/services/export_service.py` | 2 | Move to module level |

### Task 5: Create Import Organization Guidelines (S)

Document the standard import organization:

```python
"""Module docstring."""

# Standard library imports
from __future__ import annotations
import asyncio
from typing import TYPE_CHECKING
from uuid import UUID

# Third-party imports
import grpc.aio
from sqlalchemy import select

# Local imports - domain
from noteflow.domain.entities import Meeting
from noteflow.domain.errors import DomainError

# Local imports - config/constants
from noteflow.config.constants import (
    ERROR_PROJECT_ID_REQUIRED,
    ERROR_WORKSPACE_ID_REQUIRED,
    FEATURE_NAME_PROJECTS,
)

# Local imports - infrastructure
from noteflow.infrastructure.logging import get_logger

# Type checking only imports
if TYPE_CHECKING:
    from noteflow.application.services import ProjectService
```

## Deliverables

### Code Changes

- [ ] Move 20 inline imports in `project/_mixin.py` to module level
- [ ] Move inline imports in `grpc/_startup.py`
- [ ] Move inline imports in `grpc/service.py`
- [ ] Move inline imports in `grpc/server.py`
- [ ] Move inline imports in `export_service.py`
- [ ] Update any other files with 2+ inline constant imports
- [ ] Add import organization to code style documentation

### Documentation

- [ ] Document import organization standards in `CLAUDE.md`
- [ ] Add ruff rule for import ordering

## Test Strategy

### Circular Import Detection

```bash
# Run Python import check on all modified files
python -c "
import sys
modules = [
    'noteflow.grpc._mixins.project._mixin',
    'noteflow.grpc._startup',
    'noteflow.grpc.service',
    'noteflow.grpc.server',
    'noteflow.application.services.export_service',
]
for mod in modules:
    try:
        __import__(mod)
        print(f'OK: {mod}')
    except ImportError as e:
        print(f'FAIL: {mod} - {e}')
        sys.exit(1)
"
```

### Verification Tests

```python
import pytest

def test_constant_imports_at_module_level():
    """Verify no inline constant imports remain in key files."""
    import ast
    from pathlib import Path

    files_to_check = [
        "src/noteflow/grpc/_mixins/project/_mixin.py",
        "src/noteflow/grpc/_startup.py",
        "src/noteflow/grpc/service.py",
    ]

    for filepath in files_to_check:
        content = Path(filepath).read_text()
        tree = ast.parse(content)

        for node in ast.walk(tree):
            if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                for child in ast.walk(node):
                    if isinstance(child, ast.ImportFrom):
                        if child.module and "constants" in child.module:
                            pytest.fail(
                                f"Inline constant import in {filepath}:{child.lineno}"
                            )
```

## Quality Gates

### Pre-Implementation

- [ ] Run circular import check on target files
- [ ] Document current inline import count: 84

### Post-Implementation

- [ ] No circular import errors
- [ ] All tests pass
- [ ] Inline import count reduced by 50+
- [ ] Quality test passes

### Verification Commands

```bash
# Count remaining inline constant imports
grep -rn "from noteflow.config.constants import" src/noteflow/ --include="*.py" | \
  grep -E "^\s+from" | wc -l
# Target: <30 (down from 84)

# Verify no import errors
python -c "import noteflow.grpc.service"
python -c "import noteflow.grpc._mixins.project._mixin"

# Run tests
pytest tests/grpc/ -v
```

## Migration Notes

### Safe Migration Pattern

```python
# Step 1: Add module-level import
from noteflow.config.constants import ERROR_WORKSPACE_ID_REQUIRED

# Step 2: Remove inline import from function
async def CreateProject(self, ...):
    # from noteflow.config.constants import ERROR_WORKSPACE_ID_REQUIRED  # REMOVED
    if not request.workspace_id:
        await abort_invalid_argument(context, ERROR_WORKSPACE_ID_REQUIRED)
```

### Files Requiring Careful Review

Some inline imports may be intentional for:
1. **Circular dependency avoidance** - Keep inline if needed
2. **Lazy loading for startup performance** - Keep inline if hot path
3. **Conditional imports** - Keep inline if feature-flagged

## Rollback Plan

1. Inline imports can be restored one function at a time
2. No database or state changes
3. Git revert restores original import locations
4. No external dependencies affected

## References

- `src/noteflow/config/constants/__init__.py` - Constants package
- `src/noteflow/config/constants/errors.py` - Error constants
- `src/noteflow/config/constants/domain.py` - Domain constants
