Skip to content

feat: Per-model type file generation#1

Open
Pwuts wants to merge 18 commits intomainfrom
pwuts/split-type-declaration-files
Open

feat: Per-model type file generation#1
Pwuts wants to merge 18 commits intomainfrom
pwuts/split-type-declaration-files

Conversation

@Pwuts
Copy link
Copy Markdown
Member

@Pwuts Pwuts commented Jan 9, 2026

Change Summary

  • Amend src/prisma/generator/generator.py and its templates to generate a type file per DB model rather than one huge types.py
    • Minimize unused imports in .py.jinja templates
    • Use ruff to clean up generated files: eliminate unused imports and format

CI:

  • Show snapshot diff in debug mode

Checklist

  • Unit tests for the changes exist
  • Tests pass without significant drop in coverage
  • Documentation reflects changes where applicable
  • Test snapshots have been updated if applicable

Agreement

By submitting this pull request, I confirm that you can use, modify, copy and redistribute this contribution, under the terms of your choice.

@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 Security concerns

External command execution:
The generator conditionally runs ruff via subprocess.run(['ruff', ...]). While arguments are passed as a list (reducing shell-injection risk), this still executes an external binary discovered via PATH, which could be unexpected in some environments. Consider making this behavior opt-in and/or documenting it clearly, and log stderr to aid auditing/debugging.

⚡ Recommended focus areas for review

Destructive Delete

The generator unconditionally deletes the entire types/ directory via shutil.rmtree, which can be risky if rootdir is misconfigured or points somewhere unexpected. Consider adding safety checks (e.g., ensure types_dir is within the intended output tree, and/or only delete known generated files) and validating behavior for read-only files or partial permissions to avoid leaving the output in a broken state.

def _render_model_types(rootdir: Path, data: PythonData, params: Dict[str, Any]) -> None:
    """Render all type files in the types/ directory."""
    types_dir = rootdir / 'types'

    # Remove any existing types/ directory before generating
    # This prevents stale files from a previous schema from persisting
    if types_dir.exists():
        shutil.rmtree(types_dir)
        log.debug('Removed existing types directory at %s', types_dir)

    types_dir.mkdir(parents=True, exist_ok=True)
Tool Execution

Running ruff as a post-generation step changes outputs based on the environment (presence/version/config of ruff) and executes an external binary on the user’s PATH. Consider making this explicitly opt-in (config/env flag), capturing/logging stderr on failure, and ensuring generator output is deterministic when ruff is unavailable.

def _run_ruff_if_available(rootdir: Path) -> None:
    """Run ruff to fix unused imports if available."""
    try:
        # Check if ruff is available
        result = subprocess.run(
            ['ruff', '--version'],
            capture_output=True,
            check=False,
        )
        if result.returncode != 0:
            log.debug('ruff not available, skipping import cleanup')
            return

        # Run ruff to fix unused imports (F401) in the types directory
        types_dir = rootdir / 'types'
        if types_dir.exists():
            subprocess.run(
                ['ruff', 'check', '--select', 'F401', '--fix', str(types_dir)],
                capture_output=True,
                check=False,
            )
            log.debug('Ran ruff to clean up unused imports in %s', types_dir)
    except FileNotFoundError:
        log.debug('ruff not found, skipping import cleanup')
    except Exception as e:
        log.debug('Failed to run ruff: %s', e)
Missing Imports

The template uses Any in the __getattr__ signature but does not import it in this file. This relies on _header.py.jinja to provide the import; please confirm the header guarantees from typing import Any (or add the import here) to avoid runtime NameError in generated clients.

def __getattr__(name: str) -> Any:
    """Allow attribute access to types defined in submodules."""
    # Try each submodule
    import importlib
    for module_name in ['filters', 'atomic', 'list_filters'{% for model in dmmf.datamodel.models %}, '{{ model.name | lower }}'{% endfor %}]:
        try:
            module = importlib.import_module(f'.{module_name}', __package__)
            if hasattr(module, name):
                return getattr(module, name)
        except (ImportError, AttributeError):
            continue
    raise AttributeError(f"module 'prisma.types' has no attribute {name!r}")

@Pwuts Pwuts closed this Jan 9, 2026
@Pwuts Pwuts reopened this Jan 9, 2026
@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Risky Cleanup

Model type generation deletes the entire types/ directory before regenerating. This can unintentionally remove non-generated user files or other generator outputs that may have been placed under types/. Consider narrowing deletion to known generated files/patterns, writing a marker file, or generating into a dedicated subdirectory to avoid accidental data loss.

def _render_model_types(rootdir: Path, data: PythonData, params: Dict[str, Any]) -> None:
    """Render all type files in the types/ directory."""
    types_dir = rootdir / 'types'

    # Remove any existing types/ directory before generating
    # This prevents stale files from a previous schema from persisting
    if types_dir.exists():
        shutil.rmtree(types_dir)
        log.debug('Removed existing types directory at %s', types_dir)

    types_dir.mkdir(parents=True, exist_ok=True)
Tooling Side-Effect

Running ruff as part of generation introduces an external dependency and can make generation behavior environment-dependent (different ruff versions/rules) and potentially slower. Since return codes/stdout are ignored, failures may silently leave partially fixed output. Consider making this opt-in via config/env var, logging stderr on failure, and/or pinning behavior more explicitly.

def _run_ruff_if_available(rootdir: Path) -> None:
    """Run ruff to fix unused imports if available."""
    try:
        # Check if ruff is available
        result = subprocess.run(
            ['ruff', '--version'],
            capture_output=True,
            check=False,
        )
        if result.returncode != 0:
            log.debug('ruff not available, skipping import cleanup')
            return

        # Run ruff to fix unused imports (F401) in the types directory
        types_dir = rootdir / 'types'
        if types_dir.exists():
            subprocess.run(
                ['ruff', 'check', '--select', 'F401', '--fix', str(types_dir)],
                capture_output=True,
                check=False,
            )
            log.debug('Ran ruff to clean up unused imports in %s', types_dir)
    except FileNotFoundError:
        log.debug('ruff not found, skipping import cleanup')
    except Exception as e:
        log.debug('Failed to run ruff: %s', e)
Possible Import Error

The generated types/__init__.py references Any in the __getattr__ signature, but this template doesn’t import Any locally. Ensure _header.py.jinja provides from typing import Any (and that it’s not optimized away), otherwise the generated module may raise NameError at import time.

# Collect __all__ from submodules for proper re-export
def __getattr__(name: str) -> Any:
    """Allow attribute access to types defined in submodules."""
    # Try each submodule

Comment thread src/prisma/generator/generator.py
@Pwuts Pwuts force-pushed the pwuts/split-type-declaration-files branch from aed7d0c to fa72757 Compare January 13, 2026 22:55
Comment thread src/prisma/generator/templates/types/__init__.py.jinja Outdated
@Pwuts Pwuts force-pushed the pwuts/split-type-declaration-files branch from 72ca0ec to 25f871e Compare January 14, 2026 01:13
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@Pwuts Pwuts force-pushed the pwuts/split-type-declaration-files branch from 6011be7 to 5345784 Compare January 14, 2026 01:48
Comment thread src/prisma/generator/templates/types/_model.py.jinja
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant