Capstone Project

Build a complete, functional project that brings together concepts from both foundational and intermediate Python courses.

Introduction

Congratulations on reaching the capstone project! This is your opportunity to apply everything you've learned from the foundational and intermediate Python courses into a single, cohesive project. You'll create a simple but functional application that demonstrates your understanding of core Python concepts, object-oriented programming, testing, file handling, and more. This project will serve as a portfolio piece that showcases your Python skills and problem-solving abilities.

Project Purpose

The goal of this capstone project is straightforward: create and execute a task that retrieves and prints the current date and time. That's it. The functionality itself is simple and can be accomplished in just a few lines of Python code.

The Magic is in the Structure

However, the real challenge and value of this project lies not in what it does, but in how you create and structure your code. You'll be evaluated on:

  • Object-Oriented Design, Using classes, inheritance, and proper abstraction
  • Code Organization, Separating concerns into logical modules and packages
  • Best Practices, Following Python conventions, using type hints, and writing clean code
  • Testing, Writing unit tests to validate your implementation
  • Documentation, Creating clear docstrings and helpful comments

Think of this as building a professional-grade foundation for what could be a much larger application. Even simple tasks deserve well-structured, maintainable code.

First Steps

Step 1: Create Your Project Folder

Start by creating a dedicated folder for your capstone project:

$ mkdir my_simple_project
$ cd my_simple_project

Step 2: Create the Virtual Environment

Set up an isolated Python environment for your project:

$ pip install --upgrade pip virtualenv
$ virtualenv --python=python3.11 .venv
# or python -m virtualenv --python=python3.11 .venv
$ source .venv/bin/activate

Step 3: Install Required Dependencies

This project requires two external libraries: click and environs. Create a requirements.txt file at the root of your project:

click
environs

Then install them using:

$ pip install -r requirements.txt

click

A Python package for creating command-line interfaces (CLI) with minimal code. Provides decorators and utilities for building professional CLI applications.

environs

A library for managing environment variables with parsing and validation. Makes it easy to read configuration from .env files.

Project Structure

Now let's create the basic structure for our project. We'll organize our code into a dedicated service folder that will contain our application logic.

Create the Service Folder and Core Files

Create a service folder with two essential files:

$ mkdir service
$ cd service
$ touch main.py task.py

main.py

The entry point of your application. This file will contain the logic to initialize and execute your task.

task.py

The implementation of your ETL task. This file will contain the classes and logic to retrieve and process the current date and time.

Implementing task.py

The task.py file demonstrates key OOP principles by using an abstract base class to define a common interface, with concrete implementations for different task types.

task.py - Complete Implementation

# -*- coding: utf-8 -*-

from abc import ABC, abstractmethod
from datetime import datetime
from datetime import date


class MyBaseTask(ABC):
    """Basic interface"""

    @abstractmethod
    def execute(self) -> str:
        """Should be overridden"""


class MyDateTimeTask(MyBaseTask):
    """ Should return a datetime """

    def execute(self) -> str:
        return datetime.now().strftime("%Y-%m-%d %H:%M:%S")


class MyDateTask(MyBaseTask):
    """ Should return a date """

    def execute(self) -> str:
        return date.today().strftime("%Y-%m-%d")

What's Happening Here?

  • Abstract Base Class (MyBaseTask), Defines a common interface using ABC and@abstractmethod. This enforces that all subclasses must implement the execute() method.
  • Type Hints, The execute() method returns str, making the code more readable and enabling better IDE support and type checking.
  • MyDateTimeTask, A concrete implementation that returns the current date and time in YYYY-MM-DD HH:MM:SS format using datetime.now().
  • MyDateTask, Another concrete implementation that returns only the current date in YYYY-MM-DD format using date.today().

OOP Principles Demonstrated

This implementation showcases Abstraction (defining interfaces), Inheritance (subclasses extending the base class), and Polymorphism (different implementations of the same interface).

Implementing main.py

The main.py file serves as the entry point of our application. It imports the task classes, sets up logging, and executes the tasks in a clean, organized manner.

main.py - Complete Implementation

# -*- coding: utf-8 -*-

import logging
import os

from .task import MyDateTimeTask
from .task import MyDateTask


def execute() -> None:
    logger = logging.getLogger(
        name=os.getenv("APP_NAME"),
    )

    try:
        logger.info("Starting execution...")

        tasks = [
            MyDateTimeTask(),
            MyDateTask(),
        ]

        for task in tasks:
            print(task.execute())


    except Exception as error:
        logger.error(error)

    finally:
        logger.info("Execution done!")

What's Happening Here?

  • Relative Imports, Uses from .task import to import classes from the same package, following Python's package structure conventions.
  • Logging Setup, Configures a logger using logging.getLogger() with the application name from environment variables, demonstrating professional logging practices.
  • Polymorphic Execution, Creates a list of different task instances and iterates through them, calling the same execute() method on each. This showcases polymorphism in action.
  • Error Handling, Uses try-except-finally to catch and log any errors while ensuring the completion message is always logged.
  • Type Hints, The function signature includes -> None to indicate it doesn't return a value, maintaining consistency with type annotations.

Best Practices Demonstrated

This implementation demonstrates proper logging, error handling, environment variable usage, and polymorphic design patterns. The code is clean, maintainable, and follows Python conventions.

Creating the Entry Point - manager.py

Now we'll create manager.py at the root level of our project. This file serves as the main entry point for executing our code and provides a professional CLI interface using Click.

manager.py - Complete Implementation

# -*- coding: utf-8 -*-

import os

from click.core import CommandCollection
from click.decorators import group
from environs import Env


@group()
def cli_main():
    pass


@cli_main.command("run")
def main():
    from service.main import execute
    execute()
    exit()


if __name__ == "__main__":
    Env().read_env(os.path.join(os.getcwd(), ".env"))
    CommandCollection(sources=[cli_main])()

What's Happening Here?

  • Click Decorators, Uses @group() to create a command group and@cli_main.command("run") to define a CLI command that can be invoked with run.
  • Environment Loading, Uses environs to read environment variables from a.env file in the current working directory, making configuration management easy.
  • Command Collection, Creates a CommandCollection that organizes all CLI commands and makes them accessible through a single interface.
  • Lazy Import, Imports execute inside the function rather than at the module level. While imports inside functions should generally be avoided, this is one of the few valid use cases. The purpose is to isolate errors: if there's an error during service initialization, it won't crash the entire CLI when manager.py loads, preventing errors in one task from affecting other tasks.
  • Entry Point Pattern, The if __name__ == "__main__": block ensures the code only runs when the script is executed directly, not when imported as a module.

How to Run Your Project

With this setup, you can execute your project from the command line:

$ python manager.py run

This command will load your environment variables, execute both tasks, and display the current date and time.

Professional CLI Design

This implementation creates a professional command-line interface that can be easily extended with additional commands. It demonstrates proper configuration management, modular design, and follows industry-standard patterns for Python CLI applications.

Expected Output

When you run your project, you should see output similar to this:

$ python manager.py run
2023-01-19 11:26:47
2023-01-19

First Line

Output from MyDateTimeTask showing the current date and time in YYYY-MM-DD HH:MM:SS format.

Second Line

Output from MyDateTask showing only the current date in YYYY-MM-DD format.

Bonus: Python Project Template

Want to see a complete, production-ready example? Check out our Python project template that demonstrates best practices, proper structure, and professional patterns for real-world applications.

The template includes:

  • Project structure - Organized directory layout following Python best practices
  • Configuration management - Environment variables, settings, and secrets handling
  • Testing setup - Unit tests, integration tests, and CI/CD pipeline
  • Documentation - README, API docs, and contribution guidelines

This template includes additional features like Docker configuration, CI/CD pipelines, advanced testing, and more. Use it as a reference or starting point for your future Python projects!

Python IntermediateLesson Capstone