Seite 1 von 1

WxGrid und SQLAlchemy

Verfasst: Dienstag 31. Mai 2022, 09:37
von mechanicalStore
Hallo Zusammen,

da es beide Themen betrifft, setze ich das unter Allgemeine Fragen.

eine Abfrage mittels SQLAlchemy und transferieren in ein WxGrid zum viewen ist problemlos. Was aber, wenn ich die Datensätze im Grid verändern, löschen oder hinzufügen will?! Meine Suche nach einer direkten Verbindung hat nichts ergeben. Muss ich tatsächlich alles "manuell" Überwachen und entsprechend reagieren, oder gibt es eine bequemere Methode, die Datenbank upzudaten?

Danke und Grüße.

Re: WxGrid und SQLAlchemy

Verfasst: Dienstag 31. Mai 2022, 10:44
von __deets__
Wenn wx keine expliziten SQL Widgets anbiete5 (Qt macht das, aber dann ist sqlalchemy draussen, auch bei wx), dann wirst du das selbst programmieren müssen. In Python sollte es aber kein Problem sein, da einen generischen Ansatz zu entwickeln, der das einmal für alle löst.

Re: WxGrid und SQLAlchemy

Verfasst: Mittwoch 1. Juni 2022, 11:29
von mechanicalStore
Mir fehlt eigentlich ein grundlegender Ansatz. Ich habe eine Liste von Objekten , deren properties ich in String Format in das Grid bringe. Danach fehlt mir jeglicher Zusammenhang. Ich könnte 1:1 über das Grid iterieren und mit der Liste der Objekte vergleichen. Das aber würde schon dann nicht mehr funktionieren, wenn das Grid z.b. sortiert würde, da dann schon die Zuordnung zerstört ist.
Unabhängig davon eine zweite Frage; ich verstehe den gegnerischen Ansatz nicht, bzw wie würde der dabei helfen? Überhaupt wusste ich nicht, dass es bei dynamisch typisierten sprachen überhaupt generics gibt. Recherche hat ergeben, dass das mit dem typing Modul möglich ist, meintest du das?

Danke und Gruß.

Re: WxGrid und SQLAlchemy

Verfasst: Mittwoch 1. Juni 2022, 13:10
von __blackjack__
@mechanicalStore: Das klingt so als wenn Du Objekte in das WxGrid steckst, da dann bearbeitest und danach wieder die Werte aus der GUI in Objekte bringen oder damit abgleichen willst. Das wäre in der Tat aufwändig bis unmöglich. Du musst Dir zu jeder Zeile das dazugehörige Objekt merken und bei Änderungen auch gleich das Objekt ändern. Beim sortieren müssen die Objekte mitsortiert werden. Es würde sich anbieten da was von `wxGridTableBase` abzuleiten, was in wx quasi das Model zum View (`wxGrid`) ist.

Generischer Ansatz nicht als „generics“ im Sinne von Typdeklaration, sondern einfach ein Ansatz der nicht für spezielle Objekte gemacht ist, sondern allgemein für SQLAlchemy-ORM-Objekte. Die haben ja eine ganze Menge Metainformation wie Attributnamen, Datentypen, Defaultwerte, und so weiter, die man in Python alle zur Laufzeit abfragen kann. Also ist es möglich eine Klasse zu schreiben, die eine Liste mit beliebigen (gleichartigen) SQLAlchemy-ORM-Objekten bekommt, und die in einer Tabelle darstellt/bearbeitbar macht.

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 08:51
von mechanicalStore
__blackjack__ hat geschrieben: Mittwoch 1. Juni 2022, 13:10 Also ist es möglich eine Klasse zu schreiben, die eine Liste mit beliebigen (gleichartigen) SQLAlchemy-ORM-Objekten bekommt, und die in einer Tabelle darstellt/bearbeitbar macht.
Mir fehlt irgendwie der wesentliche Punkt. Ich habe hier und da ein wenig recherchiert, nach Beispielen gesucht, z.B. hier:

http://www.java2s.com/Tutorial/Python/0 ... leBase.htm

Was mir das aber nicht erklärt, denn ob ich nun strings in die Zellen von wxgrid schreibe, oder in die zugrunde liegende GridTableBase in anderem Format:

Code: Alles auswählen

class GenericTable(wx.grid.PyGridTableBase):
    def __init__(self, data, rowLabels=None, colLabels=None):
        wx.grid.PyGridTableBase.__init__(self)
        self.data = data
        ...
        ...
data = (("A", "B"), 
        ("C", "D"), 
        ("E", "F"), 
        ....
Ist doch grundsäzlich das gleiche Prinzip?!

Auch wenn die Frage hier nicht gerne gehört wird, aber hast Du einen minimalen Ansatz, der mir weiter helfen könnte, bzw. der mir auf die Sprünge hilft? Generell müsste ich doch die ORM-Objekte im Grid speichern, sonst fehlt doch nach wie vor die Zuordnung?!

Danke und Gruß

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 09:26
von __blackjack__
@mechanicalStore: Ja klar musst Du die ORM-Objekte in dem von `wx.GridTableBase` abgeleiteten Typ speichern. Und die ganzen relevanten Methoden so implementieren, dass sie das passende machen. Dazu ist diese Klasse ja da: dem `wx.Grid` eine Datenquelle so präsentieren, dass es damit etwas anfangen kann, also zwischen `wx.Grid` und Datenquelle zu vermitteln.

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 12:25
von mechanicalStore
Ein paar Fragmente zusammen gefriemelt, funktioniert alleine das schon nicht:

Code: Alles auswählen

	.....
        self.SetRowLabelValue(0, "Project")
        self.SetRowLabelValue(1, "Netplan")
        ....
Hier der minimale Ansatz zum Testen:

Code: Alles auswählen

#!/usr/bin/env python3

from sqlalchemy import (
    INTEGER,
    TEXT,
    Column,
    create_engine,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

import wx
import wx.grid

Base = declarative_base()
Session = sessionmaker()
session = 0

class Project(Base):
    __tablename__ = "project"

    id = Column(INTEGER, primary_key=True)
    name = Column(TEXT, nullable=False, unique=True)
    netplan = Column(TEXT, nullable=False)
    description = Column(TEXT, nullable=True)

class DerivedGridTable(wx.grid.GridTableBase):
    def __init__(self, *args, **kwds):
        wx.grid.GridTableBase.__init__(self, *args, **kwds)
        self.SetRowLabelValue(0, "Project")
        self.SetRowLabelValue(1, "Netplan")

# wxGlade
class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((350, 300))
        self.SetTitle("frame")
        self.panel_1 = wx.Panel(self, wx.ID_ANY)
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        self.grid_1 = wx.grid.Grid(self.panel_1, wx.ID_ANY, size=(1, 1))
        self.grid_1.CreateGrid(10, 2)
        self.table_1 = DerivedGridTable()
        self.grid_1.SetTable(self.table_1)

        sizer_1.Add(self.grid_1, 4, wx.ALL | wx.EXPAND, 5)
        self.panel_1.SetSizer(sizer_1)
        self.Layout()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

def main():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    session = Session(bind=engine)

    # Testdaten
    for i in range(5):
        project = Project(name = f"Projekt {i}", netplan = f"Netplan {i}")
        session.add(project)

    session.commit()

    # Testdaten anzeigen
    for project_name, project_netplan in (
        session.query(Project.name, Project.netplan)
        .all()
    ):
        print(project_name, project_netplan)

    app = MyApp(0)
    # app.frame.grid_1
    app.MainLoop()

if __name__ == "__main__":
    main()
Die LabelValues zu setzen sollte doch auf jeden Fall funktionieren ?!

(Editiert 13:35)

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 13:00
von __blackjack__
@mechanicalStore: Nirgendwo in dem Code wird die Verbindung zwischen dem Model und dem View hergestellt. Und wenn Du das tust, bekommst Du Fehlermeldungen weil Du mindestens die abstrakten Methoden implementieren musst. Das Grid muss ja irgendwo die Daten her bekommen, deswegen schreibt man ja die Model-Klasse.

`SetRowLabelValue()` bringt nichts weil die Default-Implementierung nichts macht. `GetRowLabelValue()` ist die Methode die Du implementieren musst wenn Du die Zeilen-Label beeinflussen willst, denn das ist ja die Methode die das Grid aufruft um an diese(n) Wert(e) zu kommen.

Beispiel mit festen Werten:

Code: Alles auswählen

class DerivedGridTable(wx.grid.GridTableBase):
    def __init__(self):
        wx.grid.GridTableBase.__init__(self)
        self.row_labels = ["Project", "Netplan"]
        
    def GetNumberRows(self):
        return len(self.row_labels)

    def GetNumberCols(self):
        return 3

    def GetRowLabelValue(self, index):
        return self.row_labels[index]
    
    def GetValue(self, row_index, column_index):
        return f"{row_index}, {column_index}"
Und das muss man halt so umschreiben, dass sich das auf irgendeine Datenstruktur bezieht, beispielsweise eine Liste mit Objekten. So das dann die `GetNumberCols()` die Länge der Liste liefert und `GetValue()` den entsprechenden Wert des entsprechenden Attributs von dem Objekt am Index.

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 14:24
von mechanicalStore
__blackjack__ hat geschrieben: Donnerstag 9. Juni 2022, 13:00 @mechanicalStore: Nirgendwo in dem Code wird die Verbindung zwischen dem Model und dem View hergestellt.
Das stimmt, da gibt es noch keine Verbindung. Ich wollte erst mal die Verbindung zwischen GridTableBase und dem Grid selbst testen, indem ich minimal die Labels verändern wollte (wieso das nicht ging, ist mir jetzt klar geworden, hoffe ich).
__blackjack__ hat geschrieben: Donnerstag 9. Juni 2022, 13:00 Und wenn Du das tust, bekommst Du Fehlermeldungen weil Du mindestens die abstrakten Methoden implementieren musst.
Ja, dass die virtuals implementiert werden müssen, ist mir klar, das hatte ich übersehen, nach dem Posten hier aufgrund der Fehlermeldungen implementiert.
__blackjack__ hat geschrieben: Donnerstag 9. Juni 2022, 13:00 `SetRowLabelValue()` bringt nichts weil die Default-Implementierung nichts macht. `GetRowLabelValue()` ist die Methode die Du implementieren musst wenn Du die Zeilen-Label beeinflussen willst, denn das ist ja die Methode die das Grid aufruft um an diese(n) Wert(e) zu kommen.
Genau darüber bin ich nun gestolpert. Ich ging davon aus, dass die GridTableBase das Model zum Grid bildet und dass das Grid immer anzeigt, was das Model gerade macht (dynamisch/automatisch?) und diese in Zusammenhang stehen (das stimmt ja auch so). Aber wenn ich in der Klasse GridTableBase set und get implementiere, dann dachte ich, dass set etwas im Grid (also in der view) setzt und vice versa get etwas aus dem Grid abruft. Aber es scheint genau umgekehrt?!

Edit: Ergo, woher weiß das Grid (die View), dass / wann es die einzelnen Funktionen der GridTableBase (also alle get's) aufrufen soll?

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 14:52
von __blackjack__
@mechanicalStore: Die Antwort auf das Edit ist vielleicht ein bisschen doof, aber das Grid weiss das weil das so programmiert ist. Es fragt halt immer GridTableBase wenn es was über die Daten wissen muss um die darzustellen.

Re: WxGrid und SQLAlchemy

Verfasst: Donnerstag 9. Juni 2022, 15:28
von mechanicalStore
Na, die Frage war ja auch doof...

Fakt ist also, dass sich das Grid (die view) ständig anhand der GridTableBase eigenständig updated?

Und wenn ich im Grid einen Wert ändere, müsste ich also im cell_changing event des Grid die (überschriebene) methode SetValue der GridTableBase aufrufen und den neuen Wert an diese übergeben?! Oder gibt es da auch wiederum (einen umgekehrten) Automatismus?

Re: WxGrid und SQLAlchemy

Verfasst: Freitag 10. Juni 2022, 11:29
von mechanicalStore
Oh Mann... jetzt beschwert sich SQLAlchemy, wenn ich einen Wert ändern will (innerhalb SetValue), was ich nicht verstehen kann:
Traceback (most recent call last):
File "c:\Users\*****\Documents\Develop\Python\SU3\test2.py", line 60, in SetValue
self.my_list[row_index].netplan = string_text
File "C:\Users\*****\AppData\Local\Programs\Python\Python39\lib\site-packages\sqlalchemy\engine\row.py", line 219, in __setattr__
raise AttributeError("can't set attribute")
AttributeError: can't set attribute
Der Übersicht halber das ganze Testprogramm:

Code: Alles auswählen

#!/usr/bin/env python3

from sqlalchemy import (
    INTEGER,
    TEXT,
    Column,
    create_engine,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, sessionmaker

import wx
import wx.grid

Base = declarative_base()
Session = sessionmaker()
session = 0

class Project(Base):
    __tablename__ = "project"

    id = Column(INTEGER, primary_key=True)
    name = Column(TEXT, nullable=False, unique=True)
    netplan = Column(TEXT, nullable=False)
    description = Column(TEXT, nullable=True)

class DerivedGridTable(wx.grid.GridTableBase):
    def __init__(self):
        wx.grid.GridTableBase.__init__(self)
        self.col_labels = ["Project", "Netplan", "Description"]
        self.my_list = []
        for project in (
            session.query(Project.name, Project.netplan, Project.description)
            .all()
        ):
            self.my_list.append(project)
        
    def GetColLabelValue(self, index):
        return self.col_labels[index]

    def GetNumberCols(self):
        return len(self.col_labels)

    def GetNumberRows(self):
        return len(self.my_list)
    
    def GetValue(self, row_index, column_index):
        if column_index == 0:
            return self.my_list[row_index].name
        elif column_index == 1:
            return self.my_list[row_index].netplan
        elif column_index == 2:
            return self.my_list[row_index].description
        # return f"{row_index}, {column_index}"

    def SetValue(self, row_index, column_index, string_text):
        if column_index == 0:
            self.my_list[row_index].name = string_text
        elif column_index == 1:
            self.my_list[row_index].netplan = string_text
        elif column_index == 2:
            self.my_list[row_index].description = string_text

        # Test, ob DB aktuell
        for project in (
            session.query(Project.name, Project.netplan, Project.description)
            .all()
        ):
            print(project.name, project.netplan, project.description)

# wxGlade
class MyFrame(wx.Frame):
    def __init__(self, *args, **kwds):
        # begin wxGlade: MyFrame.__init__
        kwds["style"] = kwds.get("style", 0) | wx.DEFAULT_FRAME_STYLE
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((350, 300))
        self.SetTitle("frame")
        self.panel_1 = wx.Panel(self, wx.ID_ANY)
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        self.grid_1 = wx.grid.Grid(self.panel_1, wx.ID_ANY, size=(1, 1))
        self.grid_1.CreateGrid(10, 2)
        self.table_1 = DerivedGridTable()
        self.grid_1.SetTable(self.table_1)

        sizer_1.Add(self.grid_1, 4, wx.ALL | wx.EXPAND, 5)
        self.panel_1.SetSizer(sizer_1)
        self.Layout()

class MyApp(wx.App):
    def OnInit(self):
        self.frame = MyFrame(None, wx.ID_ANY, "")
        self.SetTopWindow(self.frame)
        self.frame.Show()
        return True

def main():
    global session
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    session = Session(bind=engine)

    # Testdaten
    for i in range(5):
        project = Project(name = f"Projekt {i}", netplan = f"Netplan {i}", description = f"Description {i}")
        session.add(project)

    session.commit()

    # Testdaten anzeigen
    for project in (
        session.query(Project.name, Project.netplan, Project.description)
        .all()
    ):
        print(project.name, project.netplan, project.description)

    app = MyApp(0)
    # app.frame.grid_1
    app.MainLoop()

if __name__ == "__main__":
    main()

Re: WxGrid und SQLAlchemy

Verfasst: Freitag 10. Juni 2022, 13:03
von __blackjack__
@mechanicalStore: Naja das sind halt so etwas wie `namedtuple` und es macht aus ORM-Sicht auch keinen Sinn da Werte zu setzen. Du müsstest in `my_list` (sehr schlechter Name) ORM-Objekte vom Typ `Project` speichern, dann kann man auch die Attribute setzen.

`session` auf Modulebene hat da nix zu suchen. Und das mit der Zahl 0 zu initialisieren und dann später durch ein Sitzungs-Objekt zu ersetzen ist noch mal zusätzlich verwirrend.

`all()` liefert bereits eine Liste, da muss man nicht noch mal in einer Schleife jedes Element in einer andere Liste stecken.

Überarbeitet:

Code: Alles auswählen

#!/usr/bin/env python3
import wx
import wx.grid
from sqlalchemy import INTEGER, TEXT, Column, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

Base = declarative_base()
Session = sessionmaker()


class Project(Base):
    __tablename__ = "project"

    id = Column(INTEGER, primary_key=True)
    name = Column(TEXT, nullable=False, unique=True)
    netplan = Column(TEXT, nullable=False)
    description = Column(TEXT, nullable=True)


class DerivedGridTable(wx.grid.GridTableBase):
    def __init__(self, session):
        wx.grid.GridTableBase.__init__(self)
        self.session = session
        self.col_labels = ["Project", "Netplan", "Description"]
        self.attributes = ["name", "netplan", "description"]
        self.projects = self.session.query(Project).all()

    def GetColLabelValue(self, index):
        return self.col_labels[index]

    def GetNumberCols(self):
        return len(self.col_labels)

    def GetNumberRows(self):
        return len(self.projects)

    def GetValue(self, row_index, column_index):
        return getattr(self.projects[row_index], self.attributes[column_index])

    def SetValue(self, row_index, column_index, text):
        setattr(self.projects[row_index], self.attributes[column_index], text)

        # Test, ob DB aktuell
        for project in self.session.query(Project).all():
            print(project.name, project.netplan, project.description)


class Frame(wx.Frame):
    def __init__(self, session, *args, **kwds):
        wx.Frame.__init__(self, *args, **kwds)
        self.SetSize((350, 300))
        self.SetTitle("frame")
        self.panel_1 = wx.Panel(self, wx.ID_ANY)
        sizer_1 = wx.BoxSizer(wx.VERTICAL)
        self.grid_1 = wx.grid.Grid(self.panel_1, wx.ID_ANY, size=(1, 1))
        self.grid_1.CreateGrid(10, 2)
        self.table_1 = DerivedGridTable(session)
        self.grid_1.SetTable(self.table_1)

        sizer_1.Add(self.grid_1, 4, wx.ALL | wx.EXPAND, 5)
        self.panel_1.SetSizer(sizer_1)
        self.Layout()


def main():
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(engine)
    session = Session(bind=engine)

    # Testdaten
    for i in range(5):
        project = Project(
            name=f"Projekt {i}",
            netplan=f"Netplan {i}",
            description=f"Description {i}",
        )
        session.add(project)

    session.commit()

    # Testdaten anzeigen
    for project in session.query(Project).all():
        print(project.name, project.netplan, project.description)

    app = wx.App()
    frame = Frame(session, None, wx.ID_ANY, "")
    frame.Show()
    app.SetTopWindow(frame)
    app.MainLoop()


if __name__ == "__main__":
    main()
ACHTUNG: Die Testausgabe nach dem setzen des Attributs ist irreführend! Das bezieht sich nur auf diese Transaktion, die Daten sind tatsächlich erst in der Datenbank wenn man auch `commit()` aufgerufen hat!

Re: WxGrid und SQLAlchemy

Verfasst: Freitag 10. Juni 2022, 20:05
von mechanicalStore
__blackjack__ hat geschrieben: Freitag 10. Juni 2022, 13:03 @mechanicalStore: Naja das sind halt so etwas wie `namedtuple` und es macht aus ORM-Sicht auch keinen Sinn da Werte zu setzen. Du müsstest in `my_list` (sehr schlechter Name) ORM-Objekte vom Typ `Project` speichern, dann kann man auch die Attribute setzen.
Das habe ich doch gemacht, oder....
__blackjack__ hat geschrieben: Freitag 10. Juni 2022, 13:03 `all()` liefert bereits eine Liste, da muss man nicht noch mal in einer Schleife jedes Element in einer andere Liste stecken.
...war die doppelte Liste das Problem?

Code: Alles auswählen

    def GetValue(self, row_index, column_index):
    	return getattr(self.projects[row_index], self.attributes[column_index])

    def SetValue(self, row_index, column_index, text):
        setattr(self.projects[row_index], self.attributes[column_index], text)
Ich verstehe nicht, wie getattr und setaddr funktionieren. Auf Zellebene gelten row und col index, aber auf den ORM-Objekten doch nicht?! Wenn es so wäre, wie ich es verstehe (aber so ist es ja nicht...), dann müsste das so aussehen:

Code: Alles auswählen

	return getattr( (self.projects[row_index]).(self.attributes[column_index]) )
Also es ist verwirrend, dass die Methode 2 Argumente (statt 1, bzw 3 statt 2 bei setattr) mit auf den Weg bekommt.

Re: WxGrid und SQLAlchemy

Verfasst: Freitag 10. Juni 2022, 20:59
von __blackjack__
@mechanicalStore: Du hast nicht `Project`-Objekte abgefragt sondern (Named-)Tupel mit drei expliziten Attributwerten von `Project`-Objekten. Da kommt am Ende eher so etwas wie eine traditionelle Abfrage mit ”dummen” Ergebnissen heraus, und keine ORM-Objekte. Bei Deiner Abfrage war ja auch gar keine ID dabei, das heisst selbst wenn das irgendwie gehen sollte, hätte man ja gar keine Zuordnung mehr zu dem Datensatz herstellen können.

Das mit dem `all()` war kein wirkliches Problem, es ist halt nur unnötig die Liste noch mal in einer andere Liste umzukopieren.

Wie willst Du denn mit einem Argument bei `getattr()` auskommen? Das ist eine ganz normale Funktion. Das was Du da als „so müsste es aussehen“ hingeschrieben hast ist syntaktisch kein gültiges Python. Hinter dem Punkt-Operator muss ein Name stehen, literal im Quelltext, und kein Ausdruck. Die `getattr()`-Funktion braucht zwei Argumente: das Objekt und den Namen des Attributs als Zeichenkette. Wie sollte das mit weniger Argumenten gehen? Wenn das so ginge wie Du das geschrieben hast, wozu bräuchte man dann noch die `getattr()`-Funktion?

Code: Alles auswählen

In [216]: class Class: 
     ...:     def __init__(self, answer): 
     ...:         self.answer = answer 
     ...:                                                                       

In [217]: instance = Class(42)                                                  

In [218]: instance."answer"                                                     
  File "<ipython-input-218-0cfb99b48ec5>", line 1
    instance."answer"
                    ^
SyntaxError: invalid syntax


In [219]: getattr(instance."answer")                                            
  File "<ipython-input-219-e8dfc2209bbe>", line 1
    getattr(instance."answer")
                            ^
SyntaxError: invalid syntax


In [220]: getattr(instance, "answer")                                           
Out[220]: 42

Re: WxGrid und SQLAlchemy

Verfasst: Freitag 10. Juni 2022, 21:51
von mechanicalStore
@__blackjack__:Soweit alles verstanden, auch mit den namedtuple, leuchtet ein. Was getattr betrifft, da habe ich mir selbst ein Missverständnis eingebaut. Ich habe nämlich hier nachgesehen:

https://docs.wxpython.org/wx.grid.GridT ... e.GetAttr

Klar, dass ich damit dann auf dem Holzweg war. Aber wenn man es vor diesem Hintergrund betrachtet, war meine Frage ja nicht ganz so unsinnig. :mrgreen: