# Stop Hardcoding Prompts in Your Python Files: Meet PromptFill (best balance of curiosity and search terms)

> Source: <https://dev.to/pratham_shettigar_12/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-curiosity-and-search-30md>
> Published: 2026-10-22 12:10:20+00:00

Open any LLM-powered Python project that's been alive for a few weeks and you'll probably find the same thing: a function with a prompt inside it. Then the prompt grows a few more instructions, a formatting rule, an example. Before long, the logic is buried under a wall of text and nobody wants to touch the file.

That's the itch behind **PromptFill**, a small, zero-dependency Python package that lets you keep prompts in their own files and fill in the variables at runtime.

```
pip install promptfill
```

This post covers what it does, how it works under the hood (it's short enough to read in a minute), and where it fits.

Prompts aren't really code, but we treat them like code because they happen to be strings. That causes predictable problems:

Think of a form letter. Nobody retypes the whole letter for each recipient. You keep one template with blanks like *Dear ___*, and fill in the names when it's time to send. Prompts deserve the same treatment: one template file, blanks filled in by your code.

PromptFill loads a prompt template from a text file, replaces the placeholders with values you pass from Python, and returns a plain string. That's the whole job, and it's deliberately small.

`.md` or `.txt` file.`load_prompt()` and supply those values.
What you get:

`ValueError`
`FileNotFoundError`
Create `reviewer.md`:

```
You are an expert $role.

Review the following code written in $language for bugs,
security vulnerabilities, and performance issues.

Code to review:

$code_snippet

Provide your findings and suggest improvements.
```

Placeholders use a `$` prefix: `$role`, `$language`, `$code_snippet`.

``` python
from promptfill import load_prompt

prompt = load_prompt(
    file_path="reviewer.md",
    role="Senior Security Engineer",
    language="Python",
    code_snippet="print('Hello, World!')"
)

print(prompt)
You are an expert Senior Security Engineer.

Review the following code written in Python for bugs,
security vulnerabilities, and performance issues.

Code to review:

print('Hello, World!')

Provide your findings and suggest improvements.
```

The return value is a regular Python string, so it drops straight into whatever client you already use.

The whole implementation fits on one screen:

``` python
from pathlib import Path
from string import Template
from typing import Union

def load_prompt(file_path: Union[str, Path], **kwargs) -> str:
    path = Path(file_path)

    if not path.is_file():
        raise FileNotFoundError(f"Prompt file not found at: {file_path}")

    content = path.read_text(encoding="utf-8")
    template = Template(content)

    try:
        return template.substitute(**kwargs)
    except KeyError as e:
        raise ValueError(f"Missing prompt variable for placeholder: {e}")
```

Here's what happens when you call it:

`string.Template` from the contents.`$placeholder` substitution, which is why placeholders look the way they do.`substitute()` with your keyword arguments.`$name` in the template is replaced with the matching value.`Template` raises a `KeyError` internally, and PromptFill catches it and re-raises it as a I chose `string.Template` over f-strings or `str.format()` on purpose. It only cares about `$`, so curly braces in your prompt (a JSON example, say) aren't treated as placeholders. And since it's part of the standard library, there's nothing extra to install, audit, or keep updated.

The real payoff comes when one prompt serves several cases:

``` python
from promptfill import load_prompt

template_path = "reviewer.md"

python_prompt = load_prompt(
    file_path=template_path,
    role="Python Developer",
    language="Python",
    code_snippet="print(10 + 20)"
)

javascript_prompt = load_prompt(
    file_path=template_path,
    role="JavaScript Developer",
    language="JavaScript",
    code_snippet="console.log(10 + 20)"
)
```

No duplicated text and no drift between copies. Improve the wording in `reviewer.md` and every caller benefits.

Say your template expects `$role`, `$language`, and `$code_snippet`, but you forget `language`:

``` python
from promptfill import load_prompt

prompt = load_prompt(
    file_path="reviewer.md",
    role="Developer",
    code_snippet="x = 1"
)
```

You get:

```
ValueError: Missing prompt variable for placeholder: 'language'
```

It fails loudly, and it names the exact variable you forgot. That beats sending an incomplete prompt to a model and wondering why the output is strange.

Because PromptFill builds on `string.Template`, a few of its standard behaviors carry over:

`$` in a prompt (for example, a price), write `$$` in the template file.`${name}` also works.`${language}_review`.
PromptFill only formats prompts. It doesn't call any model itself, so you're free to use whichever provider you like. Here's an example with an OpenAI-compatible client:

``` python
from promptfill import load_prompt
from openai import OpenAI

client = OpenAI()

prompt = load_prompt(
    file_path="reviewer.md",
    role="Python Expert",
    language="Python",
    code_snippet="print('Hello, World!')"
)

response = client.chat.completions.create(
    model="YOUR_MODEL_NAME",
    messages=[{"role": "user", "content": prompt}]
)

print(response.choices[0].message.content)
```

The OpenAI SDK is installed separately (`pip install openai`) and only for this example. PromptFill itself stays dependency-free.

Once prompts are files, organizing them is easy:

```
my-ai-project/
├── prompts/
│   ├── reviewer.md
│   ├── summarizer.md
│   └── chatbot.txt
├── main.py
├── requirements.txt
└── README.md
```

One file per prompt, named for its purpose. It also makes prompt changes easy to review: a diff on `prompts/summarizer.md` reads like a diff on a document, not a tangle of string literals.

PromptFill is aimed at anyone who keeps prompts in Python projects:

PromptFill is a text formatter. It doesn't make prompts or inserted values safe on its own, so a few habits still apply:

PromptFill is young and the roadmap is open. Ideas I'm considering:

If one of these matters to you, tell me. Feedback shapes what gets built first.

```
pip install promptfill
```

If you try it, I'd love to hear how it goes. Open an issue, suggest a feature, or send a pull request. And if it saves you from one more 80-line string in a Python file, a star on the repo goes a long way.

How do you manage prompts in your projects: inline strings, files, or something else? Let me know in the comments.
