Kurs Python · Moduł 12: Projekt końcowy

Portfolio na GitHubie

4 min czytania
W tej lekcji6

Tropiciel, który mówi "widziałem lwa", ale nie ma żadnego zdjęcia, nie przekona nikogo. Rekruter patrzy na CV tak samo: "znam Pythona i FastAPI" to deklaracja, a repozytorium z działającym projektem to dowód. Portfolio na GitHubie to Twoja wizytówka w świecie IT, a na nim wiele osób ocenia kandydata, zanim przeczyta list motywacyjny. Pokażmy światu, co potrafisz.

Struktura repozytorium

Pierwsze wrażenie robi układ plików. Przejrzysta struktura mówi "ta osoba wie, co robi", zanim ktokolwiek otworzy kod:

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

Kod aplikacji leży w app/ z podziałem na warstwy, testy w tests/ z podziałem na unit i integration, workflowy CI w .github/workflows/. Plik .env.example zawiera nazwy zmiennych bez prawdziwych wartości, a sam .env dopisujesz do .gitignore. Plik LICENSE mówi, na jakich zasadach inni mogą używać Twojego kodu. Bez licencji obowiązuje pełne prawo autorskie, więc formalnie nikt nie może kodu kopiować ani modyfikować. Dla projektu portfolio licencja MIT to najprostszy wybór, a GitHub doda ją jednym kliknięciem przy tworzeniu repozytorium.

Zmienne środowiskowe

Skąd aplikacja bierze klucz API, skoro nie ma go w repozytorium? Z pliku .env, który czyta biblioteka python-dotenv. Funkcja load_dotenv() ładuje zmienne z pliku do środowiska procesu, a os.getenv je odczytuje:

1import os
2from dotenv import load_dotenv
3
4load_dotenv()  # wczytuje zmienne z pliku .env
5api_key = os.getenv("API_KEY")

Jeśli zmiennej nie ma, os.getenv zwraca None zamiast rzucać wyjątek. Kod się nie zmienia między laptopem a serwerem, zmienia się tylko środowisko.

Historia commitów

Rekruterzy zaglądają też w historię. Konwencja Conventional Commits standaryzuje format wiadomości: prefiks feat: oznacza nową funkcjonalność, fix: poprawkę błędu, docs: zmiany w dokumentacji, chore: prace porządkowe. Typowy przepływ to nowa gałąź, zmiany, commit, Pull Request, a na końcu code review i merge:

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

Gałąź utworzysz też nowszym poleceniem git switch -c feature/upload, efekt jest ten sam. Nawet w projekcie solo Pull Requesty pokazują, że znasz pracę zespołową.

README Template

README w portfolio sprzedaje projekt. Kluczowe elementy to tytuł z opisem, instrukcja instalacji, przykłady użycia i licencja. Plakietki na górze pokazują technologie i status testów:

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> Krótki, chwytliwy opis projektu (1-2 zdania)
9
10## Problem & Solution
11
12**Problem:** Co rozwiązuje projekt?
13**Solution:** Jak to robi?
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[Diagram architektury]
38```
39
40## Quick Start
41
42[Instrukcje instalacji]
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)

Sekcja "Problem & Solution" jest najważniejsza, bo odpowiada na pytanie "po co to istnieje". Liczby w tabeli Performance to miejsce na Twoje prawdziwe pomiary z ewaluacji, nie wpisuj wartości, których nie zmierzyłeś. Plakietka testów pobiera status z GitHub Actions, więc zielona oznacza, że testy naprawdę przechodzą.

GitHub Profile README

GitHub pokazuje README z repozytorium o tej samej nazwie co Twój login na górze profilu. To miejsce na krótkie przedstawienie się:

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

Paski umiejętności to ozdobnik, traktuj je z przymrużeniem oka. Lepiej działa link do projektu, który można uruchomić. Statystyki z github-readme-stats to zewnętrzny serwis, który bywa niedostępny, więc nie opieraj na nim całego profilu.

Wskazówki

Na profilu możesz przypiąć do sześciu repozytoriów lub gistów, więc wybierz sześć najlepszych. Regularne commity pokazują aktywność, a na zielonym wykresie kontrybucji regularność znaczy więcej niż intensywność. Każde repozytorium powinno mieć README, zawsze. Jeśli to możliwe, dodaj live demo, bo działający link przekonuje bardziej niż opis.

Moja rada: lepiej trzy dopracowane projekty z testami i README niż dwadzieścia porzuconych szkiców. W ostatniej lekcji przygotujemy się do kariery.

Pamiętaj: portfolio to zdjęcia z Twojej ekspedycji, dowód, że naprawdę przeszedłeś sawannę.

Kod do tej lekcji: 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

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Ile repozytoriów można przypiąć (pin) na profilu GitHub?

  2. 2. Który prefix commita oznacza nową funkcjonalność w Conventional Commits?

Zadania praktyczne w grze

  • Klikanie w kolejności

    Kliknij w kolejności:

  • Układanie w poziomie

    Ułóż elementy:

  • Układanie w pionie

    Uporządkuj kroki:

  • Układanie w pionie

    Które elementy są kluczowe dla dobrego README?

Przydatne artykuły