Artikel

3. Projekt-Setup

KILinuxMarketingPython

Dieses Kapitel setzt das technische Grundgerüst auf. Ziel ist kein fertiger Publisher, sondern ein lauffähiges Projekt mit Python, Playwright, OpenAI-Anbindung, Konfiguration, Verzeichnisstruktur und erstem Smoke-Test.

Die Basis bleibt bewusst schlank:

Python
Playwright
OpenAI SDK
python-dotenv
PyYAML

Playwright installiert die benötigten Browser separat; für Linux-Server kann Playwright Systemabhängigkeiten und Browser mit einem kombinierten Befehl installieren. (playwright.dev)

3.1 Python-Version und virtuelle Umgebung

Empfohlen wird Python 3.11 oder neuer.

Prüfung:

python3 --version

Projektverzeichnis anlegen:

mkdir -p social-publisher
cd social-publisher

Virtuelle Umgebung erstellen:

python3 -m venv .venv

Aktivieren:

source .venv/bin/activate

Pip aktualisieren:

python -m pip install --upgrade pip

Unter Windows wäre der Aktivierungsbefehl:

.\.venv\Scripts\Activate.ps1

Das Handbuch geht im weiteren Verlauf von Linux aus.

3.2 Abhängigkeiten installieren

Für das MVP reichen diese Pakete:

pip install openai playwright python-dotenv pyyaml pytest

Die offizielle OpenAI-Python-Bibliothek wird über das Paket openai installiert; auf PyPI ist sie als offizielles OpenAI-Paket veröffentlicht. (PyPI)

requirements.txt erzeugen:

pip freeze > requirements.txt

Eine bewusst knappe manuelle Variante:

openai
playwright
python-dotenv
pyyaml
pytest

Für reproduzierbare Produktivsysteme werden Versionen später fixiert.

3.3 Playwright installieren

Nach der Python-Paketinstallation müssen die Browser installiert werden.

Für lokalen Betrieb:

python -m playwright install chromium

Für Linux-Server mit fehlenden Systemabhängigkeiten:

sudo .venv/bin/python -m playwright install --with-deps chromium

Playwright dokumentiert playwright install, playwright install-deps und die kombinierte Variante playwright install --with-deps chromium für Browser und Systemabhängigkeiten. (playwright.dev)

Prüfen:

python -m playwright install --list

3.4 OpenAI-Client einrichten

Für das Projekt wird die OpenAI-Anbindung gekapselt. Die restliche Anwendung soll nicht direkt mit dem SDK arbeiten.

Datei:

src/openai_client.py

Inhalt:

from openai import OpenAI


class OpenAiClient:
    def __init__(self) -> None:
        self.client = OpenAI()

    def create_text(self, prompt: str) -> str:
        response = self.client.responses.create(
            model="gpt-4.1-mini",
            input=prompt,
        )

        output_text = response.output_text

        if isinstance(output_text, str) is False:
            raise RuntimeError("OpenAI response did not contain text output.")

        if output_text.strip() == "":
            raise RuntimeError("OpenAI response was empty.")

        return output_text

Der Client liest den API-Key standardmäßig aus der Umgebungsvariable OPENAI_API_KEY, sofern diese gesetzt ist. Die Verwendung des offiziellen Python-SDKs bleibt dadurch minimal. (PyPI)

3.5 .env-Konfiguration

Datei:

.env

Inhalt:

OPENAI_API_KEY=sk-...
APP_ENV=dev
APP_TIMEZONE=Europe/Berlin
DRY_RUN=true

Datei:

.env.example

Inhalt:

OPENAI_API_KEY=
APP_ENV=dev
APP_TIMEZONE=Europe/Berlin
DRY_RUN=true

Die .env gehört nicht ins Repository.

.gitignore:

.venv/
.env
__pycache__/
.pytest_cache/
storage/sessions/
storage/screenshots/
storage/logs/

Konfigurationsklasse:

src/config.py

Inhalt:

import os
from dataclasses import dataclass
from pathlib import Path

import yaml
from dotenv import load_dotenv


@dataclass(frozen=True)
class AppConfig:
    app_env: str
    timezone: str
    dry_run: bool
    root_dir: Path
    config_file: Path


class ConfigLoader:
    def __init__(self, root_dir: Path) -> None:
        self.root_dir = root_dir

    def load(self) -> AppConfig:
        load_dotenv(self.root_dir / ".env")

        app_env = os.getenv("APP_ENV")
        timezone = os.getenv("APP_TIMEZONE")
        dry_run_value = os.getenv("DRY_RUN")

        if app_env is None:
            raise RuntimeError("Missing APP_ENV.")

        if timezone is None:
            raise RuntimeError("Missing APP_TIMEZONE.")

        if dry_run_value is None:
            raise RuntimeError("Missing DRY_RUN.")

        if dry_run_value == "true":
            dry_run = True
        elif dry_run_value == "false":
            dry_run = False
        else:
            raise RuntimeError("DRY_RUN must be true or false.")

        config_file = self.root_dir / "config.yaml"

        if config_file.exists() is False:
            raise RuntimeError("Missing config.yaml.")

        with config_file.open("r", encoding="utf-8") as file:
            yaml.safe_load(file)

        return AppConfig(
            app_env=app_env,
            timezone=timezone,
            dry_run=dry_run,
            root_dir=self.root_dir,
            config_file=config_file,
        )

3.6 Verzeichnisstruktur

Anlegen:

mkdir -p data
mkdir -p prompts
mkdir -p src/platforms
mkdir -p storage/sessions
mkdir -p storage/screenshots
mkdir -p storage/images
mkdir -p storage/logs
mkdir -p tests
touch src/__init__.py
touch src/platforms/__init__.py

Zielstruktur:

social-publisher/
├── .env
├── .env.example
├── .gitignore
├── config.yaml
├── requirements.txt
├── data/
│   ├── books.json
│   ├── topics.json
│   ├── drafts.json
│   ├── approved_posts.json
│   └── published_posts.json
├── prompts/
│   └── smoke_test.txt
├── src/
│   ├── __init__.py
│   ├── main.py
│   ├── config.py
│   ├── openai_client.py
│   └── platforms/
│       └── __init__.py
├── storage/
│   ├── sessions/
│   ├── screenshots/
│   ├── images/
│   └── logs/
└── tests/

Basisdateien:

printf "[]\n" > data/books.json
printf "[]\n" > data/topics.json
printf "[]\n" > data/drafts.json
printf "[]\n" > data/approved_posts.json
printf "[]\n" > data/published_posts.json

config.yaml:

platforms:
  linkedin:
    enabled: true
    session_path: "storage/sessions/linkedin.json"
    screenshots_path: "storage/screenshots/linkedin"

limits:
  max_posts_per_day: 1
  max_comments_per_day: 3
  max_likes_per_day: 10

Prompt-Datei:

prompts/smoke_test.txt

Inhalt:

Schreibe einen sachlichen Ein-Satz-Testbeitrag für einen kleinen deutschen E-Book-Verlag. Keine Emojis. Keine Hashtags.

3.7 Erster Smoke-Test

Der Smoke-Test prüft drei Dinge:

Konfiguration wird geladen.
OpenAI antwortet.
Playwright kann Chromium starten und einen Screenshot speichern.

Datei:

src/main.py

Inhalt:

from pathlib import Path

from playwright.sync_api import sync_playwright

from src.config import ConfigLoader
from src.openai_client import OpenAiClient


def run_openai_smoke_test(root_dir: Path) -> None:
    prompt_file = root_dir / "prompts" / "smoke_test.txt"

    if prompt_file.exists() is False:
        raise RuntimeError("Missing prompts/smoke_test.txt.")

    prompt = prompt_file.read_text(encoding="utf-8")

    client = OpenAiClient()
    text = client.create_text(prompt)

    print("OPENAI_OK")
    print(text)


def run_playwright_smoke_test(root_dir: Path) -> None:
    screenshot_file = root_dir / "storage" / "screenshots" / "smoke_test.png"

    with sync_playwright() as playwright:
        browser = playwright.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto("https://example.com", wait_until="domcontentloaded")
        page.screenshot(path=str(screenshot_file), full_page=True)
        browser.close()

    if screenshot_file.exists() is False:
        raise RuntimeError("Playwright screenshot was not created.")

    print("PLAYWRIGHT_OK")
    print(str(screenshot_file))


def main() -> None:
    root_dir = Path(__file__).resolve().parent.parent

    config_loader = ConfigLoader(root_dir)
    config = config_loader.load()

    print("CONFIG_OK")
    print(config)

    run_openai_smoke_test(root_dir)
    run_playwright_smoke_test(root_dir)


if __name__ == "__main__":
    main()

Ausführen:

python -m src.main

Erwartete Ausgabe:

CONFIG_OK
AppConfig(...)
OPENAI_OK
...
PLAYWRIGHT_OK
.../storage/screenshots/smoke_test.png

Damit ist das Projekt technisch lauffähig:

Python-Umgebung steht.
Abhängigkeiten sind installiert.
Playwright startet Chromium.
OpenAI-Client erzeugt Text.
.env wird geladen.
Projektstruktur ist vorbereitet.
Smoke-Test erzeugt einen Screenshot.

0 Kommentare