You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
defcreate_invoice(
patient_id: int,
appointment_ids: list[int],
db: Session,
) ->Invoice:
"""Create a new invoice for the given appointments. Implements Business Rules: - BR-INV-01: Invoice number must be unique - BR-INV-02: Only completed appointments can be billed Args: patient_id: The ID of the patient to bill. appointment_ids: List of appointment IDs to include. db: Database session. Returns: The created Invoice object. Raises: ValueError: If no valid appointments provided. PatientNotFoundError: If patient doesn't exist. Example: >>> invoice = create_invoice(patient_id=1, appointment_ids=[1, 2], db=session) >>> print(invoice.invoice_number) 'R2025-0001' """
...
Klassen-Docstrings
classInvoiceService:
"""Service for invoice operations. Handles creation, modification, and querying of invoices. All methods are stateless and require an explicit database session. Implements Business Rules: - BR-INV-01: Invoice number format R{YEAR}-{NUMBER} - BR-INV-02: Only completed appointments can be billed - BR-INV-03: Appointments can only be billed once Attributes: None (stateless service) Example: >>> service = InvoiceService() >>> invoice = service.create_invoice(patient_id=1, appointments=[1,2], db=session) """
Import-Sortierung
Imports werden in dieser Reihenfolge sortiert (durch ruff erzwungen):
# ✅ Gut - Domain-spezifische ExceptionsclassPatientNotFoundError(Exception):
"""Raised when a patient is not found."""def__init__(self, patient_id: int):
self.patient_id=patient_idsuper().__init__(f"Patient with ID {patient_id} not found")
classInvoiceValidationError(ValueError):
"""Raised when invoice validation fails."""def__init__(self, message: str, field: str|None=None):
self.field=fieldsuper().__init__(message)
# ✅ Gut - automatisches Cleanupfromcontextlibimportcontextmanager@contextmanagerdeftemporary_file(suffix: str=".tmp"):
"""Create a temporary file that is deleted after use."""path=Path(tempfile.mktemp(suffix=suffix))
try:
yieldpathfinally:
path.unlink(missing_ok=True)
Dataclasses und Pydantic
Wann was verwenden
Use Case
Empfehlung
Einfache Datencontainer
@dataclass
API Request/Response
Pydantic BaseModel
Konfiguration
Pydantic BaseSettings
DB Models
SQLModel
UI State
@dataclass
Beispiele
fromdataclassesimportdataclass, fieldfrompydanticimportBaseModel, Field# ✅ Dataclass für internen State@dataclassclassPaginationState:
"""Pagination state for UI."""current_page: int=0page_size: int=10total_items: int=0@propertydeftotal_pages(self) ->int:
return (self.total_items+self.page_size-1) //self.page_size# ✅ Pydantic für APIclassUserCreate(BaseModel):
"""Schema for user creation request."""username: str=Field(..., min_length=3, max_length=50)
email: str=Field(..., pattern=r"^[\w\.-]+@[\w\.-]+\.\w+$")
password: str=Field(..., min_length=8)
# ✅ SQLModel für DBclassUser(SQLModel, table=True):
"""User database model."""id: int|None=Field(default=None, primary_key=True)
username: str=Field(unique=True, index=True)
email: str=Field(unique=True)
Tooling
Projekt-Tools
Tool
Zweck
Befehl
ruff
Linting & Formatting
ruff check . / ruff format .
mypy
Type Checking
mypy src/
pytest
Testing
pytest
pytest-cov
Coverage
pytest --cov=src
Vor jedem Commit
# Alle Checks auf einmal
ruff check .&& ruff format --check .&& mypy src/ && pytest
# Oder mit pre-commit hooks (empfohlen)
pre-commit run --all-files
# ❌ Schlecht - Mehrere Commitsdefbad_transaction(db: Session) ->None:
user=User(username="test")
db.add(user)
db.commit() # Commit 1order=Order(user_id=user.id)
db.add(order)
db.commit() # Commit 2 - Inkonsistenz möglich!# ✅ Gut - Ein Commit am Endedefgood_transaction(db: Session) ->None:
user=User(username="test")
db.add(user)
db.flush() # ID generieren ohne Commitorder=Order(user_id=user.id)
db.add(order)
db.commit() # Alles oder nichts
Kivy
Event-Handling
Regel
Beschreibung
Events registrieren
register_event_type() VOR super().__init__()
Unbind nach Entfernung
Widgets MÜSSEN nach remove_widget() / dismiss() unbound werden
Keine Callback-Leaks
Callbacks auf None setzen vor Cleanup
# ❌ Schlecht - Event-Leakdefremove_overlay(self):
self.overlay.dismiss()
self.overlay=None# ✅ Gut - Proper Unbindingdefremove_overlay(self):
self.overlay.unbind(on_close=self._on_overlay_close)
self.overlay.dismiss()
self.overlay=None# ✅ Gut - Events vor super().__init__() registrierenclassMyWidget(EventDispatcher):
def__init__(self, **kwargs):
self.register_event_type("on_custom_event")
super().__init__(**kwargs)
Kivy Properties vs Python Attributes
Regel
Beschreibung
Properties für UI-Bindings
StringProperty, ObjectProperty wenn KV-Bindung nötig
Normale Attribute für Internes
self._internal für nicht-reaktive Daten
Keine Class-Level mutable Defaults
_event = None auf Class-Level → in __init__
# ❌ Schlecht - Class-Level None kann zwischen Instanzen geteilt werdenclassMyController(EventDispatcher):
_poll_event=None# Problematisch!_callbacks= [] # BUG: Liste wird geteilt!# ✅ Gut - In __init__ initialisierenclassMyController(EventDispatcher):
def__init__(self, **kwargs):
super().__init__(**kwargs)
self._poll_event=Noneself._callbacks= []
KV-Language
Regel
Beschreibung
Inline KV max. 50 Zeilen
Längere Definitionen in .kv-Dateien auslagern
Keine Logik in KV
Nur Bindings, keine komplexen Ausdrücke
IDs dokumentieren
Wichtige id:-Referenzen im Docstring der Klasse erwähnen
# ✅ Gut - Kurze inline KV-DefinitionBuilder.load_string("""<StatusIndicator>: size_hint: (None, None) size: ('120dp', '24dp') Label: text: root.status_text""")
# ❌ Schlecht - Zu lange inline KV (>50 Zeilen)# → In separate .kv-Datei auslagern
Threading mit Kivy
Regel
Beschreibung
UI-Updates nur im Main Thread
Clock.schedule_once() für Thread→UI
Daemon Threads für Background
daemon=True bei kurzlebigen Tasks
importthreadingfromkivy.clockimportClock# ✅ Korrekt - Thread-safe UI Updatedefstart_background_scan(self):
defscan_in_background():
result=expensive_operation()
# UI-Update muss im Main Thread erfolgen!Clock.schedule_once(lambdadt: self._update_ui(result), 0)
thread=threading.Thread(target=scan_in_background, daemon=True)
thread.start()
Callback-Signaturen
Regel
Beschreibung
Callback-Types explizit
Callable[[NFCTagData], None] statt Callable
Nicht callable verwenden
callable ist builtin-Funktion, nicht Type Hint
fromcollections.abcimportCallable# ❌ Schlecht - callable ist keine Type Annotationdeffade_out(self, on_complete: callable=None) ->None:
...
# ✅ Gut - Explizite Callable-Signaturdeffade_out(self, on_complete: Callable[[], None] |None=None) ->None:
...
# ✅ Gut - Callback-Attribute typisierenclassDataReader:
on_data_received: Callable[[DataPacket], None] |None=Noneon_connection_lost: Callable[[], None] |None=None
Testbarkeit mit Kivy
Regel
Beschreibung
Dependency Injection
Repository/Scanner als Parameter für Tests
Keine Window-Abhängigkeit in Logik
UI-Klassen von Business-Logik trennen
Clock.tick() in Tests
Für zeitabhängige Tests Clock.tick() verwenden
# ✅ Gut - Testbar durch Dependency InjectionclassSettingsScreen(ModalView):
def__init__(
self,
repository: Repository|None=None, # DI für Testsservice: SomeService|None=None,
**kwargs,
):
self._repository=repositoryorRepository()
self._service=serviceorSomeService()