Python course Β· Module 12: Final Project

GitHub Portfolio

5 min read
In this lesson6

A tracker who says "I saw a lion" but has no photo will not convince anyone. A recruiter looks at a CV the same way: "I know Python and FastAPI" is a claim, while a repository with a working project is proof. Your GitHub portfolio is your business card in the IT world, and many people judge a candidate by it before reading the cover letter. Let's show the world what you can do.

Repository Structure

The file layout makes the first impression. A clear structure says "this person knows what they are doing" before anyone opens the code:

1ai-document-assistant/
2β”œβ”€β”€ .github/
3β”‚   β”œβ”€β”€ workflows/
4β”‚   β”‚   β”œβ”€β”€ test.yml
5β”‚   β”‚   └── deploy.yml
6β”‚   └── ISSUE_TEMPLATE/
7β”œβ”€β”€ app/
8β”‚   β”œβ”€β”€ __init__.py
9β”‚   β”œβ”€β”€ main.py
10β”‚   β”œβ”€β”€ models/
11β”‚   β”œβ”€β”€ services/
12β”‚   β”œβ”€β”€ api/
13β”‚   └── config.py
14β”œβ”€β”€ tests/
15β”‚   β”œβ”€β”€ unit/
16β”‚   β”œβ”€β”€ integration/
17β”‚   └── conftest.py
18β”œβ”€β”€ docs/
19β”‚   β”œβ”€β”€ architecture.md
20β”‚   └── api.md
21β”œβ”€β”€ docker-compose.yml
22β”œβ”€β”€ Dockerfile
23β”œβ”€β”€ requirements.txt
24β”œβ”€β”€ README.md
25β”œβ”€β”€ LICENSE
26└── .env.example

The application code lives in app/, split into layers, the tests in tests/, split into unit and integration, and the CI workflows in .github/workflows/. The .env.example file contains variable names without real values, and you add .env itself to .gitignore. The LICENSE file says on what terms others may use your code. Without a license, full copyright applies, so formally nobody may copy or modify the code. For a portfolio project the MIT license is the simplest choice, and GitHub adds it in one click when you create the repository.

Environment Variables

Where does the application get its API key if it is not in the repository? From the .env file, read by the python-dotenv library. The load_dotenv() function loads variables from the file into the process environment, and os.getenv reads them:

1import os
2from dotenv import load_dotenv
3
4load_dotenv()  # loads variables from the .env file
5api_key = os.getenv("API_KEY")

If the variable is missing, os.getenv returns None instead of raising an exception. The code stays the same between your laptop and the server, only the environment changes.

Commit History

Recruiters also look at the history. The Conventional Commits convention standardizes the message format: the feat: prefix marks a new feature, fix: a bug fix, docs: documentation changes, chore: maintenance work. The typical flow is a new branch, changes, a commit, a Pull Request, and finally code review and merge:

1git checkout -b feature/upload
2git commit -m "feat: add API endpoint"

You can also create the branch with the newer git switch -c feature/upload command, the effect is the same. Even in a solo project, Pull Requests show that you know how teamwork happens.

README Template

A portfolio README sells the project. The key elements are the title with a description, installation instructions, usage examples and the license. Badges at the top show the technologies and the test status:

1# Project Name
2
3![Python](https://img.shields.io/badge/Python-3.12-blue)
4![FastAPI](https://img.shields.io/badge/FastAPI-0.100+-green)
5![Tests](https://github.com/user/repo/actions/workflows/test.yml/badge.svg)
6![License](https://img.shields.io/badge/License-MIT-yellow)
7
8> A short, catchy description of the project (1-2 sentences)
9
10## Problem & Solution
11
12**Problem:** What does the project solve?
13**Solution:** How does it do it?
14
15## Demo
16
17![Demo GIF](docs/demo.gif)
18
19[Live Demo](https://your-demo-url.com) | [Video](https://youtube.com/...)
20
21## Tech Stack
22
23- **Backend:** Python, FastAPI, SQLAlchemy
24- **AI/ML:** LlamaIndex, OpenAI, Qdrant
25- **Infrastructure:** Docker, GitHub Actions
26- **Testing:** pytest, httpx
27
28## Features
29
30- Feature 1
31- Feature 2
32- Feature 3 (in progress)
33
34## Architecture
35
36```
37[Architecture diagram]
38```
39
40## Quick Start
41
42[Installation instructions]
43
44## Performance
45
46| Metric | Value |
47|--------|-------|
48| Response Time | <2s |
49| Accuracy | 92% |
50| Uptime | 99.9% |
51
52## Contributing
53
54Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md)
55
56## License
57
58MIT Β© [Your Name](https://github.com/username)

The "Problem & Solution" section is the most important, because it answers the question "why does this exist". The numbers in the Performance table are a place for your real measurements from evaluation, do not enter values you have not measured. The test badge takes its status from GitHub Actions, so green means the tests really pass.

GitHub Profile README

GitHub shows the README from the repository named after your username at the top of your profile. It is the place for a short introduction:

1# Hi, I'm [Name]
2
3Python Developer | AI/ML Enthusiast | Building cool stuff
4
5## Current Projects
6
7- [AI Document Assistant](link) - RAG system for document Q&A
8- [Project 2](link) - Description
9
10## Skills
11
12```
13Python     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ  95%
14FastAPI    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ   90%
15ML/AI      β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ      80%
16Docker     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ       75%
17```
18
19## GitHub Stats
20
21![Stats](https://github-readme-stats.vercel.app/api?username=yourusername)
22
23## Contact
24
25- LinkedIn: [link]
26- Email: your@email.com

Skill bars are decoration, take them with a pinch of salt. A link to a project someone can run works better. The github-readme-stats statistics come from an external service that is sometimes unavailable, so do not build your whole profile on it.

Tips

You can pin up to six repositories or gists on your profile, so choose your six best. Regular commits show activity, and on the green contribution graph consistency matters more than intensity. Every repository should have a README, always. If possible, add a live demo, because a working link convinces more than a description.

My advice: three polished projects with tests and a README beat twenty abandoned sketches. In the last lesson we will prepare for your career.

Remember: your portfolio is the photo album of your expedition, proof that you really crossed the savanna.

Code for this lesson: main.py
1# ===========================================
2# Safari Capstone: Project Setup & Configuration
3# ===========================================
4# Set up the complete project structure with
5# configuration management and logging.
6
7import logging
8import json
9from dataclasses import dataclass, field, asdict
10from pathlib import Path
11from typing import Optional
12from datetime import datetime
13
14# --- Configuration ---
15@dataclass
16class AppConfig:
17    """Application configuration for the safari system."""
18    app_name: str = "Safari Wildlife Manager"
19    version: str = "1.0.0"
20    debug: bool = False
21    database_url: str = "sqlite:///safari.db"
22    api_port: int = 8000
23    log_level: str = "INFO"
24    max_observations_per_day: int = 1000
25
26    @classmethod
27    def from_dict(cls, data: dict) -> "AppConfig":
28        return cls(**{k: v for k, v in data.items() if k in cls.__dataclass_fields__})
29
30    def to_dict(self) -> dict:
31        return asdict(self)
32
33# --- Logging setup ---
34def setup_logging(config: AppConfig):
35    """Configure application logging."""
36    logging.basicConfig(
37        level=getattr(logging, config.log_level),
38        format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
39        datefmt="%Y-%m-%d %H:%M:%S"
40    )
41    logger = logging.getLogger(config.app_name)
42    logger.info(f"Logging initialized at {config.log_level} level")
43    return logger
44
45# --- Data models ---
46@dataclass
47class Animal:
48    animal_id: str
49    species: str
50    name: str
51    weight_kg: float
52    region: str
53    status: str = "active"
54    tags: list[str] = field(default_factory=list)
55    created_at: str = field(default_factory=lambda: datetime.now().isoformat())
56
57@dataclass
58class Observation:
59    obs_id: str
60    animal_id: str
61    latitude: float
62    longitude: float
63    observer: str
64    notes: str = ""
65    timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
66
67# --- Application bootstrap ---
68config = AppConfig(debug=True, log_level="DEBUG")
69logger = setup_logging(config)
70
71print("=== Safari Project Setup ===")
72print(f"App: {config.app_name} v{config.version}")
73print(f"Debug: {config.debug}")
74print(f"Database: {config.database_url}")
75print(f"Port: {config.api_port}")
76
77logger.info("Application started")
78logger.debug("Debug mode is enabled")
79
80# Create sample data
81simba = Animal("LN-001", "Lion", "Simba", 195.0, "Savanna", tags=["alpha", "collared"])
82obs = Observation("OBS-001", "LN-001", -2.33, 34.83, "Dr. Darwin", "Morning patrol sighting")
83
84print(f"\nSample animal: {simba.name} ({simba.species})")
85print(f"Sample observation: {obs.obs_id} at ({obs.latitude}, {obs.longitude})")
86print(f"Config dict: {json.dumps(config.to_dict(), indent=2)}")
87
88# TODO: Add environment-based configuration loading
89# import os
90# def load_config() -> AppConfig:
91#     env = os.getenv("APP_ENV", "development")
92#     config_data = {
93#         "debug": env == "development",
94#         "log_level": "DEBUG" if env == "development" else "INFO",
95#         "database_url": os.getenv("DATABASE_URL", "sqlite:///safari.db"),
96#         "api_port": int(os.getenv("PORT", "8000")),
97#     }
98#     return AppConfig.from_dict(config_data)
99
100# TODO: Add a health check function
101# def health_check(config: AppConfig) -> dict:
102#     return {
103#         "status": "healthy",
104#         "version": config.version,
105#         "database": "connected",
106#         "uptime_seconds": 0
107#     }
108

Spotted a mistake in this lesson?

Check yourself

Answer the questions from this lesson. Pick an answer to see right away whether it is correct.

  1. 1. What should a professional GitHub portfolio contain?

  2. 2. What is the purpose of the Conventional Commits convention?

Hands-on tasks in the game

  • Click in order

    Click the elements to build environment variable configuration:

  • Horizontal ordering

    Arrange the git commit command with a Conventional Commits message:

  • Vertical ordering

    Arrange the Git workflow steps in the correct order:

  • Vertical ordering

    Arrange the elements of a good README from the most important to the least important:

Useful articles