IS4010: AI-Enhanced Application Development

Week 8: Building Professional Python Applications

Brandon M. Greenwell

Building Professional Python Applications ๐Ÿ—๏ธ

From scripts to production-ready tools ๐ŸŽฏ

  • Professional Python development goes beyond simple scripts to create robust, maintainable applications
  • Command-line interfaces (CLIs): The backbone of automation, scripting, and developer tools
  • Package structure: Organizing code for reusability, testing, and distribution
  • Career relevance: Every Python job expects these skills - from data science to web development
  • Midterm connection: These patterns will elevate your projects from โ€œworks on my machineโ€ to professional quality

Part 1: Command-Line Interfaces

๐Ÿค” Why CLIs matter: The power of automation

The Problem: Manual, repetitive tasks

  • Scenario: You need to fetch data from an API multiple times per day
  • Manual approach: Open browser, navigate to site, copy data, paste into file
  • Time waste: 5 minutes per fetch ร— 10 times per day = 50 minutes daily
  • Error-prone: Manual copy-paste leads to mistakes and inconsistencies
  • Solution: A CLI tool that automates the entire workflow in seconds

๐Ÿ› ๏ธ CLI tools in the wild: Examples everywhere

  • Version control: git - git add, git commit, git push
  • Package management: pip - pip install, pip list, pip freeze
  • Web frameworks: django-admin - startproject, migrate, runserver
  • AWS CLI: aws - Manage cloud infrastructure from the terminal
  • Data science: jupyter - jupyter notebook, jupyter lab
  • Your midterm: Create professional CLIs for your API projects

๐Ÿ“ฆ Introducing argparse: Pythonโ€™s built-in CLI library

  • argparse is Pythonโ€™s standard library for creating command-line interfaces
  • Built-in: No installation required, part of Pythonโ€™s standard library
  • Powerful: Handles arguments, flags, subcommands, help text, validation
  • Automatic help: Generates --help documentation for free
  • Industry standard: Used in production at Google, Netflix, and countless startups
  • Learning path: Start with argparse, graduate to Click or Typer for advanced projects

๐ŸŽฏ CLI design patterns: Arguments vs flags

Three types of CLI inputs:

  • Positional arguments: Required, order matters - git commit message.txt
  • Optional arguments (flags): Named, order flexible - git commit --amend -m "Fix bug"
  • Subcommands: Different actions in one tool - git add vs git commit

Design principles:

  • Intuitive: Users should guess the correct syntax
  • Consistent: Follow conventions from popular tools
  • Helpful: Provide clear error messages and help text
  • Forgiving: Accept common variations and abbreviations

๐Ÿ’ป Basic argparse: Your first CLI

"""simple_greeter.py - A friendly CLI greeter."""
import argparse

def main():
    # Create the parser
    parser = argparse.ArgumentParser(
        description="A simple greeting tool",
        epilog="Thanks for using the greeter!"
    )

    # Add positional argument (required)
    parser.add_argument(
        "name",
        help="The name of the person to greet"
    )

    # Add optional flag
    parser.add_argument(
        "--loud",
        action="store_true",
        help="Greet in ALL CAPS"
    )

    # Parse the arguments
    args = parser.parse_args()

    # Use the arguments
    greeting = f"Hello, {args.name}!"
    if args.loud:
        greeting = greeting.upper()

    print(greeting)

if __name__ == "__main__":
    main()

๐Ÿš€ Running the CLI: Usage examples

Getting help:

$ python simple_greeter.py --help
usage: simple_greeter.py [-h] [--loud] name

A simple greeting tool

positional arguments:
  name        The name of the person to greet

options:
  -h, --help  show this help message and exit
  --loud      Greet in ALL CAPS

Thanks for using the greeter!

Using the tool:

$ python simple_greeter.py Alice
Hello, Alice!

$ python simple_greeter.py Bob --loud
HELLO, BOB!

$ python simple_greeter.py
usage: simple_greeter.py [-h] [--loud] name
simple_greeter.py: error: the following arguments are required: name

๐ŸŽจ Advanced argparse: Multiple arguments and types

"""api_fetcher.py - Fetch data from APIs with configurable options."""
import argparse

def fetch_data(api_name: str, resource: str, limit: int, output_file: str = None):
    """Fetch data from an API (implementation details omitted)."""
    print(f"Fetching {limit} {resource} from {api_name}...")
    if output_file:
        print(f"Saving to {output_file}")
    # Actual API call would go here
    return {"status": "success", "count": limit}

def main():
    parser = argparse.ArgumentParser(
        description="Fetch data from various APIs"
    )

    # Positional arguments
    parser.add_argument("api", help="API name (e.g., 'pokemon', 'jokes', 'weather')")
    parser.add_argument("resource", help="Resource to fetch (e.g., 'pikachu', 'random')")

    # Optional arguments with types
    parser.add_argument(
        "-l", "--limit",
        type=int,
        default=10,
        help="Number of items to fetch (default: 10)"
    )

    parser.add_argument(
        "-o", "--output",
        help="Output file path (if not specified, prints to console)"
    )

    parser.add_argument(
        "--format",
        choices=["json", "csv", "txt"],
        default="json",
        help="Output format (default: json)"
    )

    args = parser.parse_args()

    # Call the function with parsed arguments
    result = fetch_data(args.api, args.resource, args.limit, args.output)
    print(f"Result: {result}")

if __name__ == "__main__":
    main()

๐ŸŒŸ Subcommands: Building multi-function tools

"""project_manager.py - Manage your project with subcommands."""
import argparse

def cmd_init(args):
    """Initialize a new project."""
    print(f"Initializing project: {args.name}")
    print(f"Template: {args.template}")

def cmd_build(args):
    """Build the project."""
    print(f"Building project...")
    if args.verbose:
        print("Verbose output enabled")

def cmd_deploy(args):
    """Deploy the project."""
    print(f"Deploying to {args.environment}")

def main():
    parser = argparse.ArgumentParser(description="Project management tool")
    subparsers = parser.add_subparsers(dest="command", help="Available commands")

    # 'init' subcommand
    init_parser = subparsers.add_parser("init", help="Initialize a new project")
    init_parser.add_argument("name", help="Project name")
    init_parser.add_argument("--template", default="basic", help="Project template")
    init_parser.set_defaults(func=cmd_init)

    # 'build' subcommand
    build_parser = subparsers.add_parser("build", help="Build the project")
    build_parser.add_argument("-v", "--verbose", action="store_true")
    build_parser.set_defaults(func=cmd_build)

    # 'deploy' subcommand
    deploy_parser = subparsers.add_parser("deploy", help="Deploy the project")
    deploy_parser.add_argument("environment", choices=["dev", "staging", "prod"])
    deploy_parser.set_defaults(func=cmd_deploy)

    args = parser.parse_args()

    # Call the appropriate function
    if hasattr(args, "func"):
        args.func(args)
    else:
        parser.print_help()

if __name__ == "__main__":
    main()

๐ŸŽฏ CLI UX best practices: Professional polish

  • Clear error messages: โ€œError: API key not found. Set POKEAPI_KEY environment variable.โ€
  • Progress indicators: Show activity for long-running operations
  • Colorized output: Use libraries like rich or colorama for visual hierarchy
  • Input validation: Fail fast with helpful messages, not cryptic stack traces
  • Exit codes: Return 0 for success, non-zero for failures (enables shell scripting)
  • Confirmation prompts: Ask before destructive operations
  • Verbose mode: --verbose or -v flag for debugging
  • Version flag: --version shows tool version

๐Ÿ’ก Practical example: API CLI for midterm projects

"""my_api_tool.py - Professional CLI for your midterm API project."""
import argparse
import sys
from typing import Optional

def fetch_resource(resource_type: str, resource_id: Optional[str], limit: int):
    """Fetch data from your API."""
    # Your API integration code here
    print(f"Fetching {resource_type}...")
    if resource_id:
        print(f"ID: {resource_id}")
    print(f"Limit: {limit}")
    # Return the data

def main():
    parser = argparse.ArgumentParser(
        prog="my_api_tool",
        description="Interact with [Your API Name] from the command line",
        epilog="Example: python my_api_tool.py pokemon pikachu --format json"
    )

    parser.add_argument("resource", help="Resource type to fetch")
    parser.add_argument("id", nargs="?", help="Optional resource ID")
    parser.add_argument("-l", "--limit", type=int, default=10, help="Number of items")
    parser.add_argument("--format", choices=["json", "pretty"], default="pretty")
    parser.add_argument("-o", "--output", help="Save to file instead of printing")

    try:
        args = parser.parse_args()
        result = fetch_resource(args.resource, args.id, args.limit)
        print("Success!")
        return 0  # Success exit code
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1  # Error exit code

if __name__ == "__main__":
    sys.exit(main())

๐Ÿš€ AI-assisted CLI development

Prompt strategies for building CLIs:

  • Starter prompt: โ€œCreate a Python argparse CLI for fetching data from the [API Name] API with options for resource type, limit, and output formatโ€
  • Enhancement prompt: โ€œAdd subcommands for โ€˜fetchโ€™, โ€˜listโ€™, and โ€˜searchโ€™ operationsโ€
  • Refinement prompt: โ€œImprove error handling and add input validation for API parametersโ€
  • Testing prompt: โ€œGenerate test cases for this CLI including error scenariosโ€

AI tools excel at:

  • Generating boilerplate argparse setup
  • Creating comprehensive help text
  • Adding input validation logic
  • Designing argument structures

Part 2: Python Packages & Project Structure

๐Ÿ—๏ธ From scripts to packages: The evolution

The journey every Python developer takes:

  • Stage 1: Single .py file - my_script.py does everything
  • Stage 2: Multiple files - utils.py, api.py, cli.py in one folder
  • Stage 3: Package structure - Organized directories with __init__.py files
  • Stage 4: Installable package - Can pip install your own code
  • Career reality: Professional projects are always Stage 3 or 4

๐Ÿค” Why package structure matters

  • Code organization: Logical grouping of related functionality
  • Reusability: Import code across multiple projects
  • Collaboration: Team members work on different modules without conflicts
  • Testing: Easier to test individual components in isolation
  • Distribution: Share your code with others via PyPI
  • Professionalism: Signals you understand production Python development

Real-world examples:

  • Django: Models, views, templates organized in packages
  • Requests: requests/, requests/auth/, requests/models/
  • Your midterm: Transform a script into a proper package structure

๐Ÿ“ฆ What is a Python package?

  • A package is a directory containing an __init__.py file
  • Module: A single .py file
  • Package: A directory of modules with __init__.py
  • Subpackage: A package inside another package
  • __init__.py: Marks directory as package, can contain initialization code
  • Modern Python (3.3+): __init__.py optional but still recommended

๐Ÿ—‚๏ธ Basic package structure: A practical example

my_api_project/
โ”‚
โ”œโ”€โ”€ my_api/                  # Main package directory
โ”‚   โ”œโ”€โ”€ __init__.py         # Marks this as a package
โ”‚   โ”œโ”€โ”€ api.py              # API client code
โ”‚   โ”œโ”€โ”€ cli.py              # CLI interface
โ”‚   โ””โ”€โ”€ utils.py            # Utility functions
โ”‚
โ”œโ”€โ”€ tests/                   # Test directory
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ test_api.py
โ”‚   โ””โ”€โ”€ test_cli.py
โ”‚
โ”œโ”€โ”€ main.py                  # Entry point script
โ”œโ”€โ”€ requirements.txt         # Dependencies
โ””โ”€โ”€ README.md               # Documentation

Using this structure:

# In main.py
from my_api.cli import main
from my_api.api import fetch_data
from my_api.utils import format_output

if __name__ == "__main__":
    main()

๐ŸŽฏ The __init__.py file: Package initialization

Empty __init__.py (most common):

# my_api/__init__.py
# Empty file - marks directory as package

With initialization code:

# my_api/__init__.py
"""My API Client Package - A professional API wrapper."""

# Import key functions for convenient access
from my_api.api import fetch_data, APIClient
from my_api.utils import format_output

# Package metadata
__version__ = "1.0.0"
__author__ = "Your Name"

# Define public API - controls "from my_api import *"
__all__ = ["fetch_data", "APIClient", "format_output"]

Usage becomes cleaner:

# Instead of: from my_api.api import fetch_data
# You can do: from my_api import fetch_data

import my_api
print(my_api.__version__)  # 1.0.0

๐Ÿ”€ Import systems: Absolute vs relative

Absolute imports (preferred):

# my_api/cli.py
from my_api.api import fetch_data
from my_api.utils import format_output
import my_api

Relative imports (within a package):

# my_api/cli.py
from .api import fetch_data        # Same directory
from .utils import format_output   # Same directory
from ..other_package import helper # Parent directory

When to use each:

  • Absolute imports: Default choice, always clear where code comes from
  • Relative imports: Useful for large packages, makes refactoring easier
  • PEP 8 recommendation: Prefer absolute imports unless path becomes excessively long

๐ŸŽญ The __name__ == "__main__" pattern

Understanding Python execution:

  • When Python runs a file directly: __name__ is set to "__main__"
  • When Python imports a file: __name__ is the module name
  • This allows code to be both a script (runnable) and a module (importable)

The pattern:

# my_api/api.py

def fetch_data(api_name: str):
    """Fetch data from an API."""
    print(f"Fetching from {api_name}")
    return {"data": "example"}

def main():
    """Entry point for running as a script."""
    result = fetch_data("pokemon")
    print(result)

# Script mode: runs when file is executed directly
if __name__ == "__main__":
    main()

Usage:

# Run as script
$ python my_api/api.py
Fetching from pokemon
{'data': 'example'}
# Import as module (main() doesn't run)
from my_api.api import fetch_data

๐Ÿ“ Modern project layout: The src/ structure

my_api_project/
โ”‚
โ”œโ”€โ”€ src/                     # Source code (modern best practice)
โ”‚   โ””โ”€โ”€ my_api/
โ”‚       โ”œโ”€โ”€ __init__.py
โ”‚       โ”œโ”€โ”€ api.py
โ”‚       โ”œโ”€โ”€ cli.py
โ”‚       โ””โ”€โ”€ utils.py
โ”‚
โ”œโ”€โ”€ tests/                   # Tests outside source tree
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ test_api.py
โ”‚   โ””โ”€โ”€ test_cli.py
โ”‚
โ”œโ”€โ”€ docs/                    # Documentation
โ”‚   โ””โ”€โ”€ index.md
โ”‚
โ”œโ”€โ”€ pyproject.toml           # Modern Python project metadata
โ”œโ”€โ”€ README.md
โ””โ”€โ”€ .gitignore

Why src/ layout?

  • Import discipline: Ensures you test installed version, not local files
  • Namespace clarity: Prevents accidental relative imports
  • Professional standard: Adopted by Python Packaging Authority (PyPA)
  • Build isolation: Cleaner separation of source and build artifacts

๐Ÿ”ง Making code installable: pyproject.toml

Modern Python packaging with pyproject.toml:

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my-api-tool"
version = "0.1.0"
description = "A professional API client for [Your API]"
authors = [{name = "Your Name", email = "you@example.com"}]
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "requests>=2.31.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0.0",
    "black>=23.0.0",
    "ruff>=0.1.0",
]

[project.scripts]
my-api = "my_api.cli:main"  # Creates a 'my-api' command

Installing in editable mode:

# Install your package locally for development
pip install -e .

# Now you can import it anywhere
python -c "from my_api import fetch_data"

# And use the CLI command
my-api pokemon pikachu

โšก Modern Python tooling: Enter uv

  • uv is a blazingly fast Python package installer and resolver (written in Rust!)
  • 10-100ร— faster than pip for most operations
  • Drop-in replacement: uv pip install works like pip install
  • Created by Astral: Same team behind Ruff (the fast Python linter)
  • Growing adoption: Becoming the standard for new Python projects
  • Learning curve: Minimal - works like pip but faster

Basic usage:

# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# Use it like pip
uv pip install requests
uv pip install -e .           # Editable install

# Create virtual environments
uv venv
source .venv/bin/activate

๐Ÿค– AI-assisted package scaffolding

Prompt strategies for project structure:

  • Initial scaffold: โ€œCreate a Python package structure for an API client project with CLI, tests, and proper importsโ€
  • Best practices: โ€œShow me a modern Python project layout with src/ structure and pyproject.tomlโ€
  • Migration: โ€œConvert my single-file script into a proper Python package with separate modulesโ€
  • Documentation: โ€œGenerate a README with installation and usage instructions for my packageโ€

AI tools excel at:

  • Generating boilerplate directory structures
  • Creating __init__.py files with proper imports
  • Writing pyproject.toml configurations
  • Setting up testing frameworks
  • Generating comprehensive README files

๐Ÿ’ก Practical example: Midterm project structure

my_midterm_project/
โ”‚
โ”œโ”€โ”€ src/
โ”‚   โ””โ”€โ”€ my_api_tool/
โ”‚       โ”œโ”€โ”€ __init__.py          # Package initialization
โ”‚       โ”œโ”€โ”€ api_client.py        # API interaction logic
โ”‚       โ”œโ”€โ”€ cli.py               # argparse CLI interface
โ”‚       โ”œโ”€โ”€ models.py            # Data models/classes
โ”‚       โ””โ”€โ”€ utils.py             # Helper functions
โ”‚
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ __init__.py
โ”‚   โ”œโ”€โ”€ test_api_client.py      # API tests
โ”‚   โ”œโ”€โ”€ test_cli.py              # CLI tests
โ”‚   โ””โ”€โ”€ test_models.py           # Model tests
โ”‚
โ”œโ”€โ”€ .github/
โ”‚   โ””โ”€โ”€ workflows/
โ”‚       โ””โ”€โ”€ tests.yml            # GitHub Actions CI/CD
โ”‚
โ”œโ”€โ”€ pyproject.toml               # Project configuration
โ”œโ”€โ”€ README.md                    # Your documentation
โ”œโ”€โ”€ .gitignore                   # Git exclusions
โ””โ”€โ”€ requirements.txt             # Or use pyproject.toml dependencies

๐Ÿ”— Putting it all together: CLI + Package structure

Entry point (main.py or src/my_api_tool/__main__.py):

"""Entry point for the my_api_tool CLI."""
from my_api_tool.cli import main

if __name__ == "__main__":
    main()

CLI module (src/my_api_tool/cli.py):

"""Command-line interface for API tool."""
import argparse
import sys
from my_api_tool.api_client import APIClient
from my_api_tool.utils import format_output

def main():
    parser = argparse.ArgumentParser(description="API client tool")
    parser.add_argument("resource", help="Resource to fetch")
    parser.add_argument("--format", choices=["json", "pretty"], default="pretty")

    args = parser.parse_args()

    try:
        client = APIClient()
        data = client.fetch(args.resource)
        print(format_output(data, args.format))
        return 0
    except Exception as e:
        print(f"Error: {e}", file=sys.stderr)
        return 1

if __name__ == "__main__":
    sys.exit(main())

Usage after install:

pip install -e .
my-api-tool pokemon --format json

๐ŸŒ‰ Bridge to Rust: A preview of next week

Python packaging challenges weโ€™ve seen:

  • Manual structure: Create directories, __init__.py files by hand
  • Import complexity: Absolute vs relative, __name__ == "__main__" patterns
  • Dependency management: Multiple tools (pip, venv, pyproject.toml)
  • Build system: Choose from many options (setuptools, hatch, poetry, PDM)

Next week in Rust:

  • Cargo handles everything automatically
  • One command: cargo new my_project creates perfect structure
  • Built-in: Package management, building, testing, documentation
  • No decisions: One standard way to structure projects
  • Comparison point: โ€œRemember Python packages? Rustโ€™s Cargo solves all those pain pointsโ€

๐Ÿ“š Resources and further learning

Official documentation:

Advanced topics:

  • PEP 8 - Python style guide
  • PEP 621 - Project metadata in pyproject.toml
  • Entry points - Creating console scripts
  • Namespace packages - Splitting packages across distributions

๐ŸŽฏ Applying to your midterm project

This weekโ€™s work session:

  • Restructure your code into a proper package with src/ layout
  • Add a CLI interface with argparse for easy testing and demonstration
  • Create clean imports between your modules
  • Add entry point in pyproject.toml for your CLI command
  • Document usage in your README with examples
  • Test your CLI with different arguments and edge cases

Success criteria:

  • Can run pip install -e . to install your project
  • CLI works with intuitive arguments and helpful error messages
  • Code is organized in logical modules with clean imports
  • --help flag provides clear usage instructions
  • README explains installation and usage

๐Ÿ“‹ Summary: Key takeaways

  • CLI with argparse: Create professional command-line tools with automatic help and validation
  • Package structure: Organize code with __init__.py, proper imports, and logical modules
  • __name__ == "__main__": Make code both runnable and importable
  • Modern tooling: pyproject.toml and uv represent the future of Python development
  • Integration: CLI + packages = professional, maintainable applications
  • Midterm application: Apply these patterns immediately to elevate your projects
  • Next week: See how Rustโ€™s Cargo handles all this automatically

๐Ÿš€ Work session: Build your professional Python application

Todayโ€™s goals:

  • Restructure your midterm project with proper package layout
  • Implement a CLI interface with argparse
  • Create clean module organization
  • Test your CLI and fix any issues
  • Update your README with installation and usage instructions

Need help?

  • Review the slide examples
  • Use AI assistants for structure scaffolding
  • Ask questions as you work
  • Reference the official documentation
  • Test frequently as you refactor

Letโ€™s build something professional! ๐ŸŽ‰