File I/O & Environment Variables

Tech Buddy June 12, 2026 3 min read
File I/O & Environment Variables

AI applications are configuration-heavy. You have API keys, model names, temperature settings, prompt template files, output directories, and environment-specific endpoints. Knowing how to handle files and environment configuration cleanly is foundational — it determines whether your app is secure, portable, and easy to run in new environments.

pathlib: The Modern Way to Handle Paths

If you've used C#'s System.IO.Path or FileInfo, you know how awkward string-based path manipulation gets. Python's pathlib module (introduced in 3.4) gives you an object-oriented path API that's cleaner, cross-platform, and composable with / as the path separator operator.

C# (System.IO)
string root = AppDomain.CurrentDomain.BaseDirectory;
                          string promptDir = Path.Combine(root, "prompts");
                          string file = Path.Combine(promptDir, "system.txt");
                          
                          bool exists = File.Exists(file);
                          string content = File.ReadAllText(file);
Python (pathlib)
from pathlib import Path
                          
                          root = Path(__file__).parent  # directory of this .py file
                          prompt_dir = root / "prompts"  # / operator joins paths
                          file = prompt_dir / "system.txt"
                          
                          exists = file.exists()
                          content = file.read_text(encoding="utf-8")

Essential pathlib Operations for AI Projects

from pathlib import Path
                          
                          # Project root detection
                          PROJECT_ROOT = Path(__file__).resolve().parent.parent
                          
                          # Common directories
                          PROMPTS_DIR = PROJECT_ROOT / "prompts"
                          DATA_DIR = PROJECT_ROOT / "data"
                          OUTPUT_DIR = PROJECT_ROOT / "outputs"
                          
                          # Create directories if missing
                          OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
                          
                          # List all .txt prompt files
                          prompt_files = list(PROMPTS_DIR.glob("*.txt"))
                          
                          # Read a prompt template
                          system_prompt = (PROMPTS_DIR / "system.txt").read_text(encoding="utf-8")
                          
                          # Write an AI response to disk
                          output_file = OUTPUT_DIR / "response_2025_06_07.txt"
                          output_file.write_text(response_text, encoding="utf-8")
                          
                          # Path introspection
                          print(output_file.name)       # "response_2025_06_07.txt"
                          print(output_file.stem)       # "response_2025_06_07"
                          print(output_file.suffix)     # ".txt"
                          print(output_file.parent)     # outputs directory path

Reading and Writing Files

Text Files: Prompt Templates and Logs

from pathlib import Path
                          from datetime import datetime
                          
                          def load_prompt_template(template_name: str) -> str:
                              """Load a prompt template from the prompts directory."""
                              template_path = PROMPTS_DIR / f"{template_name}.txt"
                              if not template_path.exists():
                                  raise FileNotFoundError(f"Prompt template not found: {template_path}")
                              return template_path.read_text(encoding="utf-8")
                          
                          
                          def save_response(prompt: str, response: str, tag: str = "") -> Path:
                              """Save a prompt-response pair to disk for review."""
                              timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
                              filename = f"response_{timestamp}{'_' + tag if tag else ''}.txt"
                              output = OUTPUT_DIR / filename
                          
                              content = f"=== PROMPT ===\n{prompt}\n\n=== RESPONSE ===\n{response}\n"
                              output.write_text(content, encoding="utf-8")
                              return output
                          
                          
                          # Using open() for streaming or append mode:
                          def append_to_log(message: str, log_file: Path) -> None:
                              with open(log_file, "a", encoding="utf-8") as f:
                                  timestamp = datetime.now().isoformat()
                                  f.write(f"[{timestamp}] {message}\n")

JSON Files: Saving Structured AI Outputs

import json
                          from pathlib import Path
                          
                          def save_evaluation_results(results: list[dict], output_path: Path) -> None:
                              """Save structured evaluation data as pretty-printed JSON."""
                              with open(output_path, "w", encoding="utf-8") as f:
                                  json.dump(results, f, indent=2, ensure_ascii=False)
                          
                          
                          def load_evaluation_results(path: Path) -> list[dict]:
                              """Load evaluation results from JSON."""
                              if not path.exists():
                                  return []
                              return json.loads(path.read_text(encoding="utf-8"))
                          
                          
                          # Example usage
                          results = [
                              {"prompt_id": "p001", "model": "claude-3-5-sonnet", "score": 0.92, "latency_ms": 1240},
                              {"prompt_id": "p002", "model": "claude-3-5-sonnet", "score": 0.85, "latency_ms": 980},
                          ]
                          save_evaluation_results(results, OUTPUT_DIR / "eval_results.json")

Environment Variables and Configuration

AI applications need to run in at least three contexts: local development, CI/CD, and production. Configuration that works in all three uses environment variables — not hardcoded values, not config files with secrets baked in.

The .env Pattern

# .env — local development secrets (NEVER commit to git)
                          ANTHROPIC_API_KEY=sk-ant-api03-xxxx
                          OPENAI_API_KEY=sk-xxxx
                          PINECONE_API_KEY=xxxx
                          APP_ENV=development
                          MODEL_NAME=claude-3-5-sonnet-20241022
                          MAX_TOKENS=1024
                          LOG_LEVEL=DEBUG
# .env.example — committed to git as a template
                          ANTHROPIC_API_KEY=your_key_here
                          OPENAI_API_KEY=your_key_here
                          PINECONE_API_KEY=your_key_here
                          APP_ENV=development
                          MODEL_NAME=claude-3-5-sonnet-20241022
                          MAX_TOKENS=1024
                          LOG_LEVEL=INFO
# src/config.py — centralized configuration loader
                          from __future__ import annotations
                          import os
                          from pathlib import Path
                          from dotenv import load_dotenv
                          
                          # Load .env file — does nothing if already loaded or running in CI with real env vars
                          load_dotenv()
                          
                          
                          def require_env(key: str) -> str:
                              """Get an environment variable or raise a clear error."""
                              value = os.environ.get(key)
                              if not value:
                                  raise EnvironmentError(
                                      f"Required environment variable '{key}' is not set. "
                                      f"Add it to your .env file or set it in your environment."
                                  )
                              return value
                          
                          
                          def optional_env(key: str, default: str) -> str:
                              """Get an optional environment variable with a fallback."""
                              return os.environ.get(key, default)
                          
                          
                          # Application configuration as module-level constants
                          ANTHROPIC_API_KEY = require_env("ANTHROPIC_API_KEY")
                          APP_ENV = optional_env("APP_ENV", "development")
                          MODEL_NAME = optional_env("MODEL_NAME", "claude-3-5-sonnet-20241022")
                          MAX_TOKENS = int(optional_env("MAX_TOKENS", "1024"))
                          LOG_LEVEL = optional_env("LOG_LEVEL", "INFO")

Using Configuration in Your Application

# main.py
                          import anthropic
                          from src.config import ANTHROPIC_API_KEY, MODEL_NAME, MAX_TOKENS
                          
                          # Use the config — no env access scattered throughout codebase
                          client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
                          
                          response = client.messages.create(
                              model=MODEL_NAME,
                              max_tokens=MAX_TOKENS,
                              messages=[{"role": "user", "content": "Hello!"}],
                          )

Environment-Specific Configuration

Production AI applications often need different settings per environment. A clean pattern uses an APP_ENV variable to switch behavior without code changes:

# src/config.py (extended)
                          import os
                          from dataclasses import dataclass
                          from dotenv import load_dotenv
                          
                          load_dotenv()
                          
                          @dataclass(frozen=True)
                          class AppConfig:
                              anthropic_api_key: str
                              model_name: str
                              max_tokens: int
                              log_level: str
                              is_production: bool
                              output_dir: str
                          
                          
                          def load_config() -> AppConfig:
                              env = os.environ.get("APP_ENV", "development")
                              is_production = env == "production"
                          
                              return AppConfig(
                                  anthropic_api_key=os.environ["ANTHROPIC_API_KEY"],
                                  model_name=os.environ.get(
                                      "MODEL_NAME",
                                      "claude-3-5-sonnet-20241022" if is_production else "claude-3-haiku-20240307",
                                  ),
                                  max_tokens=int(os.environ.get("MAX_TOKENS", "1024")),
                                  log_level=os.environ.get("LOG_LEVEL", "WARNING" if is_production else "DEBUG"),
                                  is_production=is_production,
                                  output_dir=os.environ.get("OUTPUT_DIR", "./outputs"),
                              )
                          
                          
                          # Singleton config loaded once at startup
                          config = load_config()

Processing Prompt Template Files

Storing prompt templates in files instead of hardcoding them in Python strings gives you several benefits: version-controlled prompts, non-programmer access to prompt engineering, and easy A/B testing of prompt variants.

# prompts/code_review.txt
                          """
                          You are a senior Python engineer performing a code review.
                          Focus on: {focus_areas}
                          
                          Review the following code for issues related to correctness,
                          performance, security, and readability. Be specific and cite
                          line references where possible.
                          
                          Code language: {language}
                          Context: {context}
                          """
                          
                          # src/prompts.py
                          from pathlib import Path
                          from typing import Any
                          
                          PROMPTS_DIR = Path(__file__).parent.parent / "prompts"
                          
                          
                          def load_template(name: str, **variables: Any) -> str:
                              """
                              Load a prompt template and fill in variables.
                              Variables use Python's .format() syntax: {variable_name}
                              """
                              template_path = PROMPTS_DIR / f"{name}.txt"
                              raw_template = template_path.read_text(encoding="utf-8").strip()
                          
                              if variables:
                                  try:
                                      return raw_template.format(**variables)
                                  except KeyError as e:
                                      raise ValueError(f"Template '{name}' is missing variable: {e}")
                          
                              return raw_template
                          
                          
                          # Usage
                          code_review_prompt = load_template(
                              "code_review",
                              focus_areas="security, error handling",
                              language="Python",
                              context="Production AI pipeline processing 10k req/day",
                          )
                          
                          print(code_review_prompt[:100])

Key Takeaways

  • pathlib.Path is the modern Python file API — use the / operator to join paths, read_text() and write_text() for simple I/O
  • Use Path(__file__).parent to build paths relative to the current Python file — portable across machines and OSes
  • Store all secrets in .env files and load with python-dotenv; commit only .env.example with placeholder values
  • Create a central config.py module that loads all env vars once at startup — never scatter os.getenv() calls throughout the codebase
  • Use os.environ["KEY"] (not os.getenv()) for required config — fail loud and early on missing values
  • Store prompt templates as .txt files in a versioned prompts/ directory — enables non-code prompt iteration and git history track