Skip to main content

Module Generation: Complete Prompt Guide

Generating a complete module—multiple related functions, classes, and utilities—is more complex than single-function generation. You must define the module's public API, clarify which functions are internal, manage dependencies between functions, and specify how they fit together. This article teaches the module-level prompt pattern and how to structure specifications for larger code units.

Anatomy of a Module

A module is typically organized as:

my_module/
__init__.py # Exports public classes/functions
core.py # Main logic (internal)
utils.py # Helper functions (internal)
errors.py # Custom exceptions (public)

When generating a module, you must specify:

  1. Public API: What functions/classes are exported and used by other code?
  2. Internal structure: How are internal helpers organized?
  3. Interdependencies: Which functions call which?
  4. Error handling: Custom exception types and error semantics?
  5. Configuration: Settings, constants, and defaults?

The Module-Level Prompt Template

Here's a reusable template:

**Module Overview**
Name: [module_name]
Purpose: [1-2 sentence description of what the module does]
Language & Version: [Python 3.11 / JavaScript ES2022 / Rust 1.75]

**Public API**
Functions/Classes exported from this module:
- ClassName(...) → [what it does]
- function_name(...) → [what it does]

**Data Structures**
Define any dataclasses, classes, or type definitions:
class MyDataClass:
field1: Type
field2: Type

**Internal Functions**
Helper functions (prefixed with _, not exported):
- _helper_function(...)
- _parse_data(...)

**Interdependencies**
- ClassName depends on: helper_function_1, helper_function_2
- function_name calls: ClassName.__init__, _helper_function

**Error Handling**
Custom exceptions:
class MyModuleError(Exception): ...
class ValidationError(MyModuleError): ...

**Requirements by Function**
For each public function/class, list requirements and examples

**Module-Level Constraints**
- Code style: Black, PEP 8
- Type checking: mypy strict
- Performance targets: [if applicable]
- Dependencies: [libraries that may be imported]

Real Example: A Configuration File Parser Module

Let's generate a complete module that reads and validates configuration files:

**Module Overview**
Name: config_parser
Purpose: Read, parse, and validate application configuration files in YAML format
Language & Version: Python 3.11

**Public API**
- load_config(filepath: str) -> Config
Load and parse a YAML config file, return Config object

- Config (dataclass)
Represents parsed configuration with validation
Fields: app_name (str), port (int), debug (bool), plugins (list[str])

**Data Structures**
```python
from dataclasses import dataclass

@dataclass
class Config:
app_name: str
port: int
debug: bool
plugins: list[str]

Internal Functions

  • _validate_config(data: dict) -> list[str] Return list of validation errors (empty if valid)
  • _parse_yaml(content: str) -> dict Parse YAML string, raise ParseError on invalid YAML

Error Handling

class ConfigError(Exception):
"""Base exception for config module"""
pass

class ParseError(ConfigError):
"""Raised when YAML parsing fails"""
pass

class ValidationError(ConfigError):
"""Raised when config validation fails"""
pass

Function 1: load_config Signature: def load_config(filepath: str) -> Config:

Requirements:

  1. Read YAML file from filepath
  2. Parse YAML using yaml library (PyYAML 6.0+)
  3. Validate parsed data (call _validate_config)
  4. If validation fails, raise ValidationError with all errors listed
  5. Deserialize validated dict to Config dataclass
  6. Return Config object
  7. Raise FileNotFoundError if file doesn't exist
  8. Docstring with example

Examples:

  • load_config("config.yaml") where config.yaml contains:

    app_name: MyApp
    port: 8080
    debug: false
    plugins: [auth, logging]

    Returns: Config(app_name="MyApp", port=8080, debug=False, plugins=["auth", "logging"])

  • load_config("/nonexistent.yaml") Raises: FileNotFoundError

  • load_config("bad.yaml") where bad.yaml is invalid YAML Raises: ParseError("Invalid YAML: ...")

  • load_config("incomplete.yaml") where port is missing Raises: ValidationError("port: required field missing")

Function 2: _validate_config (internal) Signature: def _validate_config(data: dict) -> list[str]:

Requirements:

  1. Check app_name is string, non-empty
  2. Check port is int, in range 1-65535
  3. Check debug is bool
  4. Check plugins is list of strings
  5. Return list of error strings (e.g., ["port: out of range", "plugins: not a list"])
  6. Return empty list if all validations pass
  7. No exceptions (just return errors)

Examples:

  • _validate_config({"app_name": "App", "port": 8080, "debug": False, "plugins": []}) Returns: []

  • _validate_config({"app_name": "", "port": 99999, "debug": "yes"}) Returns: ["app_name: cannot be empty", "port: out of range (1-65535)", "debug: must be boolean"]

Function 3: _parse_yaml (internal) Signature: def _parse_yaml(content: str) -> dict:

Requirements:

  1. Parse YAML string using yaml.safe_load
  2. If parsing fails, catch yaml.YAMLError and raise ParseError with original message
  3. If result is not a dict, raise ParseError("YAML must be a mapping")
  4. Return the parsed dict

Examples:

  • _parse_yaml("key: value") → {"key": "value"}
  • _parse_yaml("[invalid") → raises ParseError("...")
  • _parse_yaml("- item1\n- item2") → raises ParseError("YAML must be a mapping")

Module-Level Constraints

  • Code style: PEP 8, Black (line length 88)
  • Type hints: required for all functions
  • Docstrings: Google style
  • No type: ignore comments unless unavoidable
  • Error handling: no bare except, no pass in except blocks
  • Module initialization: all = ["Config", "load_config", "ConfigError", "ValidationError", "ParseError"]

Required Tests (not generated, but reference for correctness) Test load_config with valid YAML file Test load_config with missing file Test load_config with malformed YAML Test load_config with invalid values Test _validate_config with all combinations of valid/invalid fields


This comprehensive prompt gives the AI model everything needed to generate a coherent, integrated module without gaps or misunderstandings.

## Organizing Module Prompts for Large Libraries

For larger libraries (5+ functions, multiple classes), split into multiple prompts:

**Prompt 1: Core Data Structures**
Define the dataclasses, enums, and types used throughout the library.

**Prompt 2: Internal Utilities**
Generate helper functions and validators.

**Prompt 3: Public API**
Generate the public-facing functions and classes that depend on the above.

**Prompt 4: Integration**
Generate the `__init__.py` file that ties everything together and exports the public API.

For example:

Prompt 1: Data Structures (Prompt A)

Define: User, Post, Comment dataclasses Define: exceptions: UserNotFound, PostNotFound

Prompt 2: Utilities (Prompt B, depends on A)

Generate: _validate_email(), _sanitize_html(), _hash_password()

Prompt 3: Database Layer (Prompt C, depends on A and B)

Generate: UserRepository, PostRepository classes

Prompt 4: Facades and Init (Prompt D, depends on A, B, C)

Generate: application-level functions like get_user(), create_post() Generate: init.py with all


This modular approach is easier than one massive prompt and allows iteration on each layer.

## Key Considerations for Module Generation

**Circular dependencies.** If FunctionA calls FunctionB and FunctionB calls FunctionA, specify this explicitly. The AI model must understand the circular dependency to generate correct code. Consider refactoring to break the cycle if possible.

**Module initialization.** Specify what happens when the module is imported:

Module initialization (init.py):

  • Import and expose: Config, load_config, ConfigError from core
  • Set all = ["Config", "load_config", "ConfigError"]
  • No side effects (no automatic file reads or API calls)

**Logging and debugging.** If the module logs activity, specify the logging pattern:

Logging:

  • Use Python logging module (not print)
  • Logger name: name (module-level logger)
  • Levels: DEBUG for internal steps, INFO for significant events
  • Example: logger.debug(f"Parsing config from {filepath}")

**Configuration defaults.** If the module has configurable behavior, specify defaults clearly:

Defaults:

  • Timeout: 30 seconds
  • Retry attempts: 3
  • Cache size: 1000 items
  • Log level: INFO

## Table: Function vs. Module Prompts

| Aspect | Function Prompt | Module Prompt |
|---|---|---|
| Scope | Single function (50–300 LOC) | Multiple functions (200–1000 LOC) |
| Specification | Function signature, 5–10 requirements, 3 examples | Data structures, public API, internal helpers, 20+ requirements, 10+ examples |
| Iteration | Usually 1 attempt | 2–4 attempts (layer by layer) |
| Error handling | One error type or exception | Multiple custom exceptions |
| Dependencies | May call stdlib | May call multiple external libraries |
| Testing | Unit test per function | Integration tests between functions |

## Key Takeaways

- Module prompts require specifying data structures, public API, internal helpers, and interdependencies.
- Use the module-level template: overview, public API, data structures, internal functions, error handling, requirements per function, constraints.
- For large libraries, split into multiple prompts (one per architectural layer).
- Specify module initialization: what is exported (`__all__`), what happens on import.
- Define custom exceptions and error semantics clearly.

## Frequently Asked Questions

### Should I generate the entire module in one prompt?

For modules under 500 lines, yes—one prompt is cleaner. For modules over 500 lines, split into 2–3 prompts by layer (data structures, then utilities, then public API). This reduces cognitive load on the AI model and allows iterative refinement.

### What if two functions depend on each other?

Specify the dependency explicitly. Example: "ClassA.__init__ calls _initialize_b(), which may call ClassA.get_status()." The AI model will understand the circular dependency and generate code that handles it (usually through careful initialization order).

### How do I specify module-level configuration or constants?

Define them in a Constants section:

Module Constants: DEFAULT_TIMEOUT = 30 # seconds MAX_RETRIES = 3 SUPPORTED_FORMATS = ["yaml", "json", "toml"]


Include the constants section in your prompt and the AI model will generate them in the module.

### Should I include `__init__.py` in the prompt, or generate it separately?

Generate it separately after the core module is done. Once you have all functions and classes, write a simple prompt for `__init__.py` that imports and exports them. This avoids coupling the `__init__.py` to the implementation details.

### Can I test module generation locally before it's used in production?

Absolutely. Generate the module, run tests locally, review the code for errors and style, then deploy. Generate-test-review is the standard workflow. If the module fails tests, revise your prompt and regenerate.

## Further Reading

- [Python Packaging: Module Structure](https://packaging.python.org/en/latest/layout.html)
- [Google: Software Architecture Guide](https://google.github.io/styleguide/)
- [Martin Fowler: Modular Design Patterns](https://www.martinfowler.com/articles/modular-design.html)
- [Rust Book: Modules and Packages](https://doc.rust-lang.org/book/ch07-00-managing-growing-projects-with-packages-modules-and-paths.html)