Kurs Python · Moduł 12: Projekt końcowy
Portfolio na GitHubie
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.exampleKod 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
4
5
6
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
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
22
23## Contact
24
25- LinkedIn: [link]
26- Email: your@email.comPaski 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# }
108Widzisz błąd w tej lekcji?
Sprawdź się
Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.
1. Ile repozytoriów można przypiąć (pin) na profilu GitHub?
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?