Python

How I Used Python to Auto-Doc My freeCodeCamp Project

Finished a freeCodeCamp project but dreading the documentation? Learn how I used a simple Python script with the 'inspect' module to automate my docs.

L

Liam Carter

A Python enthusiast and software developer passionate about automation and clean code.

6 min read16 views

There’s a unique kindof euphoria that comes with finishing a big project. For me, it was completing the "Data Analysis with Python" certification on freeCodeCamp. I’d wrangled NumPy arrays, tamed Pandas DataFrames, and visualized my way to the finish line. My code worked, the tests passed, and I was ready to add it to my portfolio. Then I looked at my project folder.

It was a digital mess. A collection of Python files with names like main_final.py and data_cleaner_v2.py. Inside, a labyrinth of functions with cryptic names and zero comments. I felt a familiar dread creep in—the documentation phase. The part of the project that feels like doing the dishes after hosting a fantastic dinner party.

But this time, I had an idea. What if I could use the very tool I'd just mastered—Python—to do the boring work for me? What if I could make my code document itself?

The Post-Project Hangover: A Wall of Undocumented Code

Let’s be honest. When you’re in the zone, deep in problem-solving, writing comments is the last thing on your mind. You’re trying to make the logic work, not explain it for your future self. My project, an analysis of video game sales data, was full of functions like this:

# A function I wrote at 2 AM
def process(df, year):
    df_filtered = df[df['Year'] > year]
    df_filtered['Name'] = df_filtered['Name'].str.strip()
    df_filtered.dropna(subset=['Publisher'], inplace=True)
    return df_filtered

It works, sure. But what does it really do? Why did I choose to filter by year? What side effects does dropna have with inplace=True? Six months from now, would I have a clue? Probably not. The thought of going through dozens of these functions and manually writing a README.md was soul-crushing.

The "Aha!" Moment: If It's Repetitive, Automate It

As programmers, our mantra is to automate repetitive tasks. We write scripts to deploy websites, process data, and organize files. So why were we still documenting by hand? The information was already in the code, just not in a readable format. The bridge between the code and the documentation was missing.

The solution was simple in concept:

  1. Write clear, structured comments directly inside my functions.
  2. Write a Python script to scan my project file.
  3. This script would pull out those comments and function names.
  4. Finally, it would format everything into a beautiful, clean Markdown file.

This wasn't about finding a complex documentation generator like Sphinx, which felt like overkill for a single-file project. This was about a lightweight, custom solution that I could build in under an hour.

Advertisement

The Foundation: Writing Docstrings That Don't Suck

The first step was to go back and refactor my code, but not by changing the logic. I just needed to add proper docstrings. A docstring isn’t just any comment; it’s a string literal that occurs as the first statement in a module, function, class, or method definition. It's accessible at runtime, which is the key to our automation.

I decided to adopt the Google Python Style for my docstrings. It's readable and structured. Here’s that same messy function, but refactored with a proper docstring:

def process_sales_data(df, min_year):
    """Filters and cleans the video game sales DataFrame.

    This function takes a raw DataFrame of sales data, filters out entries
    before a specified year, cleans game titles, and removes rows with
    missing publisher information.

    Args:
        df (pd.DataFrame): The raw DataFrame of video game sales.
        min_year (int): The minimum year of release to include in the data.

    Returns:
        pd.DataFrame: A cleaned and filtered DataFrame.
    """
    df_filtered = df[df['Year'] > min_year]
    df_filtered['Name'] = df_filtered['Name'].str.strip()
    df_filtered.dropna(subset=['Publisher'], inplace=True)
    return df_filtered

Now we’re talking! It’s instantly clear what the function does, what it needs (its arguments), and what it gives back (its return value). This isn't just for our script; it makes the code itself a million times more maintainable. Plus, most code editors will show you this docstring as a tooltip when you use the function.

The Magic Wand: Python's `inspect` Module

With my docstrings in place, I needed a way for a script to *read* them. This is where Python's standard library shines. The inspect module is a powerful tool for introspection—the ability of a program to examine its own structure.

The inspect module lets you do things like get the members of a live object, retrieve the source code of a function, and, most importantly for us, pull out its docstring.

Here’s a tiny snippet to see how it works. Imagine you have a file called my_project.py with the function above. You could write another script to inspect it:

import inspect
import my_project

# Get all members (functions, classes, etc.) from the my_project module
for name, member in inspect.getmembers(my_project):
    # Check if the member is a function
    if inspect.isfunction(member):
        print(f"Function Name: {name}")
        # Get the clean, un-indented docstring
        docstring = inspect.getdoc(member)
        print(f"Docs:\n{docstring}")
        print("---")

Running this would print the function name and the beautifully formatted docstring we wrote earlier. The magic was happening! I had all the building blocks I needed.

Putting It All Together: The Auto-Doc Script

Now for the main event. I created a new file called generate_docs.py. This script would be my personal documentation assistant. Its job is to import my main project file, inspect it, and write the output to DOCUMENTATION.md.

Here’s a simplified version of the final script:

import inspect
import sys

# --- CONFIGURATION ---
# The name of the Python file you want to document (without .py)
TARGET_MODULE_NAME = "game_sales_analyzer"
OUTPUT_FILENAME = "DOCUMENTATION.md"
PROJECT_TITLE = "Video Game Sales Analysis"


def generate_documentation():
    """Inspects a Python module and generates a Markdown documentation file."""
    try:
        module = __import__(TARGET_MODULE_NAME)
    except ImportError:
        print(f"Error: Could not import module '{TARGET_MODULE_NAME}'. Make sure it's in the same directory.")
        sys.exit(1)

    markdown_content = f"# {PROJECT_TITLE} Documentation\n\n"
    markdown_content += "This file was automatically generated from the project's docstrings.\n\n"

    # Find all functions in the module
    functions = [ 
        member for name, member in inspect.getmembers(module) 
        if inspect.isfunction(member) and member.__module__ == module.__name__
    ]

    if not functions:
        markdown_content += "No functions with docstrings found in the module."
    else:
        markdown_content += "## Functions\n\n"

    for func in functions:
        signature = inspect.signature(func)
        docstring = inspect.getdoc(func)

        markdown_content += f"### `__{func.__name__}{signature}__`\n\n"
        
        if docstring:
            # A simple way to format the 'Args:' and 'Returns:' sections
            formatted_doc = docstring.replace("Args:", "**Args:**").replace("Returns:", "**Returns:**")
            markdown_content += f"{formatted_doc}\n\n"
        else:
            markdown_content += "_No documentation available._\n\n"
        
        markdown_content += "---\n"

    # Write the content to the output file
    with open(OUTPUT_FILENAME, "w") as f:
        f.write(markdown_content)

    print(f"Successfully generated documentation at '{OUTPUT_FILENAME}'!")

if __name__ == "__main__":
    generate_documentation()

I just had to run python generate_docs.py from my terminal, and like magic, a new file appeared.

The Glorious Result: From Chaos to Clarity

The generated DOCUMENTATION.md file was exactly what I needed. It was clean, professional, and—best of all—required zero manual copy-pasting. Every function from my project was listed with its signature and its perfectly formatted docstring.

Now, when I push my project to GitHub, I don't just have a folder of code. I have a welcoming entry point. Anyone (including my future self) can open the documentation and immediately understand what each part of the project does. It elevated my freeCodeCamp project from a simple exercise to a portfolio-worthy piece of work.

Beyond the Project: A New Habit Formed

This little experiment fundamentally changed my workflow. I no longer see documentation as a chore to be done at the end. I see it as an integral part of writing code. By writing good docstrings as I go, I'm not just preparing for some future documentation step; I'm making my code better and easier to reason about right now.

So next time you finish a project and face that wall of undocumented code, don't despair. Take a moment, channel your inner programmer, and ask yourself: "How can I automate this?" You might be surprised at how a simple script can turn your biggest headache into your proudest feature.

Tags

You May Also Like