After writing Python professionally for 6 years, I've settled on a boilerplate that saves me from the same 5 bugs every time.
Here are the 15 lines I paste at the top of every new script:
#!/usr/bin/env python3
"""[Description of what this script does]."""
import argparse
import json
import logging
import sys
from pathlib import Path
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger(__name__)
Let me explain why each line matters.
Line 1: The Shebang
#!/usr/bin/env python3
Without this, ./script.py fails on Linux/Mac. With it, the system finds Python automatically. env python3 is more portable than /usr/bin/python3 because Python might be installed anywhere.
Lines 4-7: The Right Imports
import argparse
import json
import logging
import sys
from pathlib import Path
argparse — Every script eventually needs command-line arguments. Adding it later means refactoring sys.argv hacks. Start with it.
json — You'll need to read or write JSON. You always do.
logging — print() is for users. log.info() is for debugging. The difference matters when your script runs in production at 3 AM.
sys — For sys.exit(1) on errors and sys.stdin/sys.stdout for piping.
pathlib.Path — Better than os.path in every way:
# os.path (ugly)
path = os.path.join(os.path.dirname(__file__), 'data', 'config.json')
# pathlib (clean)
path = Path(__file__).parent / 'data' / 'config.json'
Lines 9-14: Logging Setup
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%H:%M:%S",
)
log = logging.getLogger(__name__)
This gives you timestamped, leveled output:
14:23:01 INFO Starting data fetch
14:23:05 WARNING API returned 429, retrying
14:23:08 ERROR Failed after 3 retries
The Main Block I Add Next
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("input", help="Input file or URL")
parser.add_argument("-o", "--output", default="-", help="Output file (default: stdout)")
parser.add_argument("-v", "--verbose", action="store_true")
args = parser.parse_args()
if args.verbose:
logging.getLogger().setLevel(logging.DEBUG)
log.info("Starting %s", args.input)
# ... actual logic here ...
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
sys.exit(130)
except Exception as e:
log.error("Fatal: %s", e)
sys.exit(1)
The KeyboardInterrupt handler prevents ugly tracebacks when users press Ctrl+C. The generic Exception handler ensures the script always exits cleanly with a proper error code.
What's In Your Boilerplate?
Do you have a standard template for new scripts? What language? What do you always include?
I build developer tools and write about Python, security, and APIs. 130+ open source repos.
Top comments (0)