diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e68cb81 --- /dev/null +++ b/.env.example @@ -0,0 +1,8 @@ +# APP_MODE ungenutzt im Template +APP_MODE= + +# LOGLEVEL: "CRITICAL"- "ERROR" - "WARNING" - "INFO" - "DEBUG" +LOGLEVEL=INFO + +# Pfad zum log z.B log/template.log +LOGFILE=log/template.log diff --git a/.gitignore b/.gitignore index 0a19790..382b128 100644 --- a/.gitignore +++ b/.gitignore @@ -129,6 +129,8 @@ celerybeat.pid # Environments .env +.env.* +!.env.example .venv env/ venv/ @@ -172,3 +174,9 @@ cython_debug/ # PyPI configuration file .pypirc + +# Custom: +logs/ +log/ +*.zip + diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..efcd059 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,7 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-python.vscode-pylance", + "ms-toolsai.jupyter" + ] + } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..789e0ce --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,24 @@ +{ + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.linting.mypyEnabled": true, + "python.linting.pylintArgs": ["--disable=C0114,C0115,C0116"], + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.organizeImports": "explicit" + }, + "python.analysis.typeCheckingMode": "basic", + + // Abschliessende Leerzeichen entfernen: + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + + // Markdown für das Entfernen von abschliessenden Leerzeichen rausnehmen: + "[markdown]": { + "files.trimTrailingWhitespace": false + }, + "[python]": { + "editor.tabSize": 4, + "editor.insertSpaces": true + } + } diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..388da81 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,17 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Linter (pylint)", + "type": "shell", + "command": "pylint beispiel.py", + "group": "build" + }, + { + "label": "Typprüfung (mypy)", + "type": "shell", + "command": "mypy beispiel.py", + "group": "build" + } + ] + } diff --git a/README.md b/README.md index 31a49fc..0e415e0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # Python Bootstrap-Template mit `.venv` und `.env` Support -Dieses Projekt bietet ein portables Start-Template für Python-Anwendungen mit folgenden Features: +Dieses Template nutzt PEP 8, Type Hints, Docstrings und einen vordefinierten Workspace für sauberen Python-Code. +Ausserdem bietet es ein portables Start-Template für Python-Anwendungen mit folgenden Features: - Automatische Erstellung einer virtuellen Umgebung (`.venv`) - Automatische Installation von Abhängigkeiten aus `requirements.txt` @@ -9,6 +10,10 @@ Dieses Projekt bietet ein portables Start-Template für Python-Anwendungen mit f - Sauberer Einstiegspunkt über `run.py` - Keine systemweiten Python-Pakete notwendig +Das Template ist durchdacht, pragmatisch und stark auf Entwicklerkomfort ausgelegt. +Es bietet eine sehr gute Grundlage für Projekte aller Art – insbesondere CLI-Tools, kleine Services und lokale Anwendungen. +Die automatische Einrichtung der virtuellen Umgebung hebt es funktional deutlich von Standard-Vorlagen ab. + --- - [Python Bootstrap-Template mit `.venv` und `.env` Support](#python-bootstrap-template-mit-venv-und-env-support) @@ -19,8 +24,14 @@ Dieses Projekt bietet ein portables Start-Template für Python-Anwendungen mit f - [⚙️ .env-Datei (optional)](#️-env-datei-optional) - [📜 Beispielausgabe](#-beispielausgabe) - [🧼 Optional: `.env.example`](#-optional-envexample) + - [🪵 Logging](#-logging) + - [🔧 Konfiguration (in `.env`)](#-konfiguration-in-env) + - [📥 Beispielausgabe](#-beispielausgabe-1) + - [📌 Logik im Code](#-logik-im-code) + - [📁 Logrotation](#-logrotation) - [🛠 Hinweise](#-hinweise) - [🧪 Getestet mit](#-getestet-mit) + - [🛠 Einsatz Linter (`pylint`)](#-einsatz-linter-pylint) - [📁 Lizenz](#-lizenz) --- @@ -29,14 +40,20 @@ Dieses Projekt bietet ein portables Start-Template für Python-Anwendungen mit f ```plaintext -template-root/ -├── .env # Projektkonfiguration (optional, wird automatisch geladen) -├── requirements.txt # Abhängigkeiten (z. B. python-dotenv) -├── run.py # Einstiegspunkt für die Anwendung -└── app/ - ├── __init__.py - ├── main.py # Hauptlogik der Anwendung - └── bootstrap.py # Setup- und Relaunch-Logik +📁 template-root/ +├── 📁 .vscode/ # Projekteinstellungen VS-Code +│ ├── 📄 settings.json # Einstellungen +│ └── 📄 extensions.json # Erweiterungen +├── 📄 .env # Projektkonfiguration (optional, nicht im git) +├── 📄 .env.example # Vorlage der .env +├── 📄 requirements.txt # Abhängigkeiten (z.B. python-dotenv) +├── 📄 README.md # diese Datei +├── 📄 VERSION # Versionsinfo zum Paket +├── 📄 run.py # Einstiegspunkt für die Anwendung +└── 📁 app/ # + ├── 📄 __init__.py # + ├── 📄 main.py # Hauptlogik der Anwendung + └── 📄 bootstrap.py # Setup- und Relaunch-Logik ``` --- @@ -49,17 +66,18 @@ python run.py ### Beim ersten Start passiert: -1. `.venv` wird erstellt (wenn noch nicht vorhanden) -2. `requirements.txt` wird installiert -3. Das Skript wird automatisch innerhalb der venv neu gestartet -4. `.env` wird geladen (falls vorhanden) -5. Die App startet +1. `.venv` wird erstellt (wenn noch nicht vorhanden) +2. `requirements.txt` wird installiert +3. Das Skript wird automatisch innerhalb der venv neu gestartet +4. `.env` wird geladen (falls vorhanden) +5. Die App startet --- ## 📦 Abhängigkeiten -Alle Abhängigkeiten werden aus `requirements.txt` installiert. Beispiel: +Alle Abhängigkeiten werden aus `requirements.txt` installiert. +**Beispiel:** ```text python-dotenv @@ -69,7 +87,8 @@ python-dotenv ## ⚙️ .env-Datei (optional) -Wenn vorhanden, wird `.env` automatisch geladen. Beispiel: +Wenn vorhanden, wird `.env` automatisch geladen. +**Beispiel:** ```dotenv APP_MODE=development @@ -77,26 +96,26 @@ LOGLEVEL=debug PORT=8080 ``` -Diese Werte sind im Code über `os.getenv("APP_MODE")` verfügbar. +Diese Werte sind im Code über `os.getenv("APP_MODE")` verfügbar. --- ## 📜 Beispielausgabe ```text -[BOOTSTRAP] Erstelle virtuelle Umgebung... -[BOOTSTRAP] Installiere pip + requirements.txt... -[BOOTSTRAP] Starte in virtueller Umgebung neu... -[RUN] Lade .env aus: ./cliqrcode/.env -[APP] Starte Anwendung im Modus: development -[APP] Hello, world! +[BOOTSTRAP] Erstelle virtuelle Umgebung... +[BOOTSTRAP] Installiere pip + requirements.txt... +[BOOTSTRAP] Starte in virtueller Umgebung neu... +[RUN] Lade .env aus: ./cliqrcode/.env +[APP] Starte Anwendung im Modus: development +[APP] Hello, world! ``` --- ## 🧼 Optional: `.env.example` -Erstelle eine `.env.example`, um deine Konfiguration zu dokumentieren: +Erstelle eine `.env.example`, um deine Konfiguration zu dokumentieren: ```dotenv APP_MODE=production @@ -106,11 +125,61 @@ LOGLEVEL=info --- +## 🪵 Logging + +Dieses Template verwendet ein integriertes Logging-Modul mit folgenden Eigenschaften: + +- Ausgabe in die Konsole (immer aktiv) +- Optionale Ausgabe in eine Logdatei (`LOGFILE`) +- Unterstützung für rotierende Logdateien +- Loglevel konfigurierbar über `.env` +- Plattformunabhängig (Windows, Linux, macOS) +- Keine externen Abhängigkeiten + +### 🔧 Konfiguration (in `.env`) + +```dotenv +LOGLEVEL=INFO # Möglich: DEBUG, INFO, WARNING, ERROR, CRITICAL +LOGFILE=logs/app.log # Optionaler Pfad zur Logdatei (relativ oder absolut) +``` + +> Wenn `LOGFILE` nicht gesetzt ist, wird nur in die Konsole geloggt. + +### 📥 Beispielausgabe + +```bash +[2025-04-23 14:10:00] INFO app.main: Template ready. +[2025-04-23 14:10:00] DEBUG app.main: Dies ist eine Debug-Meldung. +``` + +### 📌 Logik im Code + +In beliebigen Modulen kannst du so einen Logger verwenden: + +```python +from app.logging_utils import get_logger + +log = get_logger(__name__) +log.info("Template ready.") +``` + +### 📁 Logrotation + +Die Logdatei wird bei 1 MB automatisch rotiert (max. 3 Backups), z. B.: + +```bash +logs/app.log +logs/app.log.1 +logs/app.log.2 +``` + +--- + ## 🛠 Hinweise -- Das Template ist portabel und benötigt keine global installierten Pakete. -- Du kannst es für jede neue App wiederverwenden. -- `run.py` ist der einzige Einstiegspunkt – keine direkten Aufrufe von `main.py`. +- Das Template ist portabel und benötigt keine global installierten Pakete. +- Du kannst es für jede neue App wiederverwenden. +- `run.py` ist der einzige Einstiegspunkt – keine direkten Aufrufe von `main.py`. --- @@ -122,6 +191,32 @@ LOGLEVEL=info --- +## 🛠 Einsatz Linter (`pylint`) + + + +```cmd +PS C:\Users\adams\Documents\template> .\.venv\Scripts\activate +``` + +```cmd +(.venv) PS C:\Users\adams\Documents\template> pylint.exe run.py +``` + +```cmd +************* Module run +run.py:27:4: C0412: Imports from package app are not grouped (ungrouped-imports) +run.py:12:0: W0611: Unused import os (unused-import) + +----------------------------------- +Your code has been rated at 8.33/10 +``` + +**Bonus:** +Durch den Einsatz der <.vscode/task.json> für VS-Code, kannst du in VS-Code mit `Strg + Umschalt + P` → `Tasks: Run Task` → `Linter (pylint)` oder `Typprüfung (mypy)` aufrufen. + +--- + ## 📁 Lizenz -MIT – frei verwendbar in eigenen Projekten. \ No newline at end of file +MIT – frei verwendbar in eigenen Projekten. diff --git a/VERSION b/VERSION index 6c6aa7c..3eefcb9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.1.0 \ No newline at end of file +1.0.0 diff --git a/app/__init__.py b/app/__init__.py index e69de29..c82a910 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -0,0 +1,8 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Hier liegen die Dateien für die primäre Logik der Anwendung + +Diese Information hier stammt aus der datei ./app/__init__.py +""" diff --git a/app/bootstrap.py b/app/bootstrap.py index 8aeec20..3118381 100644 --- a/app/bootstrap.py +++ b/app/bootstrap.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ Bootstrap-Modul für automatische Einrichtung und Start der App. @@ -8,8 +10,8 @@ Dieses Modul stellt sicher, dass: """ import os -import sys import subprocess +import sys from pathlib import Path # Pfad zur virtuellen Umgebung im Projektverzeichnis @@ -17,14 +19,15 @@ VENV_DIR = Path(__file__).resolve().parent.parent / ".venv" # Pfad zum Python-Interpreter in der venv PYTHON_EXE = VENV_DIR / ("Scripts" if os.name == "nt" else "bin") / "python" + def ensure_venv(): """ Prüft, ob die .venv existiert und ob das aktuelle Skript bereits innerhalb der venv ausgeführt wird. Falls nicht, wird: - - die venv erstellt + - die .venv erstellt - requirements.txt installiert - - das Skript in der venv neu gestartet + - das Skript in der .venv neu gestartet """ if os.environ.get("BOOTSTRAPPED") == "1": return # Bereits innerhalb der .venv → nichts tun @@ -35,6 +38,7 @@ def ensure_venv(): if Path(sys.executable).resolve() != PYTHON_EXE.resolve(): _relaunch() + def _create_venv(): """ Legt eine virtuelle Umgebung im Projektverzeichnis an @@ -48,9 +52,14 @@ def _create_venv(): req_file = Path(__file__).resolve().parent.parent / "requirements.txt" if req_file.exists(): - subprocess.check_call([str(PYTHON_EXE), "-m", "pip", "install", "-r", str(req_file)]) + subprocess.check_call( + [str(PYTHON_EXE), "-m", "pip", "install", "-r", str(req_file)] + ) else: - print("[BOOTSTRAP] ⚠️ Keine requirements.txt gefunden – Installation übersprungen.") + print( + "[BOOTSTRAP] ⚠️ Keine requirements.txt gefunden – Installation übersprungen." + ) + def _relaunch(): """ diff --git a/app/logging_utils.py b/app/logging_utils.py new file mode 100644 index 0000000..8bf0ee3 --- /dev/null +++ b/app/logging_utils.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +Zentrales Logging-Modul +→ Unterstützt LOGLEVEL und LOGFILE aus der .env +→ Plattformunabhängig (Windows/Linux) +→ Erstellt automatisch das Log-Verzeichnis bei Bedarf +→ Fällt bei ungültigem Log-Level sicher auf INFO zurück +""" + +import logging +import os +from logging.handlers import RotatingFileHandler +from pathlib import Path + + +def safe_log_level(level_str: str) -> int: + """Wandelt einen Level-String in einen gültigen Logging-Level um.""" + levels = { + "CRITICAL": logging.CRITICAL, + "ERROR": logging.ERROR, + "WARNING": logging.WARNING, + "INFO": logging.INFO, + "DEBUG": logging.DEBUG, + "NOTSET": logging.NOTSET, + } + return levels.get(level_str.upper(), logging.INFO) + + +LOGLEVEL = safe_log_level(os.getenv("LOGLEVEL", "INFO")) +LOGFILE = os.getenv("LOGFILE") # z. B. logs/app.log + + +def get_logger(name: str) -> logging.Logger: + logger = logging.getLogger(name) + if logger.handlers: + return logger # Logger bereits konfiguriert + + logger.setLevel(LOGLEVEL) + + formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(name)s: %(message)s") + + # Konsole + console_handler = logging.StreamHandler() + console_handler.setFormatter(formatter) + logger.addHandler(console_handler) + + # Datei (optional) + if LOGFILE: + logfile_path = Path(LOGFILE) + try: + logfile_path.parent.mkdir(parents=True, exist_ok=True) + file_handler = RotatingFileHandler( + logfile_path, maxBytes=1_000_000, backupCount=3, encoding="utf-8" + ) + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + except Exception as e: + logger.warning(f"Konnte Logdatei nicht schreiben: {e}") + + return logger diff --git a/app/main.py b/app/main.py index 38a0609..01b8957 100644 --- a/app/main.py +++ b/app/main.py @@ -1,12 +1,39 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ +./app/main.py + Hier beginnt deine eigentliche Anwendung. Alle Konfigurationen aus .env sind jetzt über os.getenv() verfügbar. """ import os +from app.logging_utils import get_logger + +log = get_logger(__name__) + + def main(): - mode = os.getenv("APP_MODE", "development") - print(f"[APP] Starte Anwendung im Modus: {mode}") - print("[APP] Hello, world!") + """ + Hier liegen die Dateien für die primäre Logik der Anwendung + Diese Information hier stammt aus der datei ./app/__init__.py + """ + # Hole APP_MODE aus der .env + mode = os.getenv("APP_MODE", "DEVEL") + + # Testausgabe: + print(f"[APP] 🚀 Starte Anwendung im Modus: {mode}") + print("[APP] 📦 -= Hello, world! =-") + + +def logtest(): + """ + wirft testweise alle Logvarianten aus. + """ + log.info("Template ready.") + log.debug("Dies ist eine Debug-Meldung.") + log.warning("Dies ist eine Warnung.") + log.error("Dies ist eine Fehlermeldung.") + log.critical("Dies ist eine kritische Meldung.") diff --git a/requirements.txt b/requirements.txt index 566cccb..75542a5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,16 @@ +# -*- coding: utf-8 -*- + +# falls man .env verwenden möchte: python-dotenv + +# Projektbezogen ab hier: + + +# --- ALLES AB HIER IST OPTIONAL --- + +# Pytest, Linter und Typprüfung: +pytest +pytest-cov +pytest-mock +pylint +mypy diff --git a/run.py b/run.py index e810cc3..e7ed1e8 100644 --- a/run.py +++ b/run.py @@ -1,28 +1,39 @@ #!/usr/bin/env python3 +# -*- coding: utf-8 -*- """ - Einstiegspunkt der Anwendung. - Sorgt dafür, dass beim ersten Start automatisch: - - eine virtuelle Umgebung (.venv) angelegt wird - - alle Abhängigkeiten installiert werden - - das Skript in der .venv neu gestartet wird - Erst danach wird die .env geladen und die App gestartet. +Einstiegspunkt der Anwendung. + +Sorgt dafür, dass beim ersten Start automatisch: +- eine virtuelle Umgebung (.venv) angelegt wird +- alle Abhängigkeiten installiert werden +- das Skript in der .venv neu gestartet wird +Erst danach wird die .env geladen und die App gestartet. """ import os + from app.bootstrap import ensure_venv if __name__ == "__main__": ensure_venv() # .env laden – jetzt ist python-dotenv installiert (innerhalb der venv) - from dotenv import load_dotenv, find_dotenv + from dotenv import find_dotenv, load_dotenv + dotenv_path = find_dotenv() if dotenv_path: - print(f"\n[RUN] Lade .env aus: {dotenv_path}") + print(f"\n[RUN] 🚀 Lade .env aus: {dotenv_path}") load_dotenv(dotenv_path=dotenv_path, override=True) else: - print("[RUN] ⚠️ Keine .env-Datei gefunden") + print("\n[RUN] ⚠️ Keine .env-Datei gefunden") + + # Ab hier deine Funktionen aufrufen: from app.main import main + main() + + from app.main import logtest + + logtest()