{"slug": "stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and", "title": "Stop Hardcoding Prompts in Your Python Files: Meet PromptFill (best balance of curiosity and search terms)", "summary": "A developer released PromptFill, a zero-dependency Python package that loads prompt templates from .md or .txt files and substitutes $placeholder variables at runtime using the standard library's string.Template. The tool raises FileNotFoundError for missing files and ValueError for missing variables, and was designed to avoid treating curly braces in prompts as placeholders.", "body_md": "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.\n\nThat'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.\n\n```\npip install promptfill\n```\n\nThis post covers what it does, how it works under the hood (it's short enough to read in a minute), and where it fits.\n\nPrompts aren't really code, but we treat them like code because they happen to be strings. That causes predictable problems:\n\nThink 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.\n\nPromptFill 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.\n\n`.md` or `.txt` file.`load_prompt()` and supply those values.\nWhat you get:\n\n`ValueError`\n`FileNotFoundError`\nCreate `reviewer.md`:\n\n```\nYou are an expert $role.\n\nReview the following code written in $language for bugs,\nsecurity vulnerabilities, and performance issues.\n\nCode to review:\n\n$code_snippet\n\nProvide your findings and suggest improvements.\n```\n\nPlaceholders use a `$` prefix: `$role`, `$language`, `$code_snippet`.\n\n``` python\nfrom promptfill import load_prompt\n\nprompt = load_prompt(\n    file_path=\"reviewer.md\",\n    role=\"Senior Security Engineer\",\n    language=\"Python\",\n    code_snippet=\"print('Hello, World!')\"\n)\n\nprint(prompt)\nYou are an expert Senior Security Engineer.\n\nReview the following code written in Python for bugs,\nsecurity vulnerabilities, and performance issues.\n\nCode to review:\n\nprint('Hello, World!')\n\nProvide your findings and suggest improvements.\n```\n\nThe return value is a regular Python string, so it drops straight into whatever client you already use.\n\nThe whole implementation fits on one screen:\n\n``` python\nfrom pathlib import Path\nfrom string import Template\nfrom typing import Union\n\ndef load_prompt(file_path: Union[str, Path], **kwargs) -> str:\n    path = Path(file_path)\n\n    if not path.is_file():\n        raise FileNotFoundError(f\"Prompt file not found at: {file_path}\")\n\n    content = path.read_text(encoding=\"utf-8\")\n    template = Template(content)\n\n    try:\n        return template.substitute(**kwargs)\n    except KeyError as e:\n        raise ValueError(f\"Missing prompt variable for placeholder: {e}\")\n```\n\nHere's what happens when you call it:\n\n`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.\n\nThe real payoff comes when one prompt serves several cases:\n\n``` python\nfrom promptfill import load_prompt\n\ntemplate_path = \"reviewer.md\"\n\npython_prompt = load_prompt(\n    file_path=template_path,\n    role=\"Python Developer\",\n    language=\"Python\",\n    code_snippet=\"print(10 + 20)\"\n)\n\njavascript_prompt = load_prompt(\n    file_path=template_path,\n    role=\"JavaScript Developer\",\n    language=\"JavaScript\",\n    code_snippet=\"console.log(10 + 20)\"\n)\n```\n\nNo duplicated text and no drift between copies. Improve the wording in `reviewer.md` and every caller benefits.\n\nSay your template expects `$role`, `$language`, and `$code_snippet`, but you forget `language`:\n\n``` python\nfrom promptfill import load_prompt\n\nprompt = load_prompt(\n    file_path=\"reviewer.md\",\n    role=\"Developer\",\n    code_snippet=\"x = 1\"\n)\n```\n\nYou get:\n\n```\nValueError: Missing prompt variable for placeholder: 'language'\n```\n\nIt 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.\n\nBecause PromptFill builds on `string.Template`, a few of its standard behaviors carry over:\n\n`$` in a prompt (for example, a price), write `$$` in the template file.`${name}` also works.`${language}_review`.\nPromptFill 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:\n\n``` python\nfrom promptfill import load_prompt\nfrom openai import OpenAI\n\nclient = OpenAI()\n\nprompt = load_prompt(\n    file_path=\"reviewer.md\",\n    role=\"Python Expert\",\n    language=\"Python\",\n    code_snippet=\"print('Hello, World!')\"\n)\n\nresponse = client.chat.completions.create(\n    model=\"YOUR_MODEL_NAME\",\n    messages=[{\"role\": \"user\", \"content\": prompt}]\n)\n\nprint(response.choices[0].message.content)\n```\n\nThe OpenAI SDK is installed separately (`pip install openai`) and only for this example. PromptFill itself stays dependency-free.\n\nOnce prompts are files, organizing them is easy:\n\n```\nmy-ai-project/\n├── prompts/\n│   ├── reviewer.md\n│   ├── summarizer.md\n│   └── chatbot.txt\n├── main.py\n├── requirements.txt\n└── README.md\n```\n\nOne 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.\n\nPromptFill is aimed at anyone who keeps prompts in Python projects:\n\nPromptFill is a text formatter. It doesn't make prompts or inserted values safe on its own, so a few habits still apply:\n\nPromptFill is young and the roadmap is open. Ideas I'm considering:\n\nIf one of these matters to you, tell me. Feedback shapes what gets built first.\n\n```\npip install promptfill\n```\n\nIf 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.\n\nHow do you manage prompts in your projects: inline strings, files, or something else? Let me know in the comments.", "url": "https://wpnews.pro/news/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and", "canonical_source": "https://dev.to/pratham_shettigar_12/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-curiosity-and-search-30md", "published_at": "2026-10-22 12:10:20+00:00", "updated_at": "2026-10-22 12:12:05.114544+00:00", "lang": "en", "topics": ["ai-tools", "developer-tools", "large-language-models", "ai-products"], "entities": ["PromptFill", "Python"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and", "markdown": "https://wpnews.pro/news/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and.md", "text": "https://wpnews.pro/news/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and.txt", "jsonld": "https://wpnews.pro/news/stop-hardcoding-prompts-in-your-python-files-meet-promptfill-best-balance-of-and.jsonld"}}