__str__ und file.write

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Du darfst da nix aufrufen. Entry.start ist alles, was die Spalte identifiziert. Das ist doch eine Klasse, keine Instanz, entsprechend hat start auch garkeinen Wert, auf dem "date()" aufrufbar waere.

Als rechte Seite muss ein datetime-Objekt angegeben werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Also erst einmal funktioniert ``and`` bei dem `where` so nicht, denn das ist ja kein SQL sondern ein Python-``and`` und das wird von Python ausgewertet bevor `where()` aufgerufen wird *und* der Operator ist in Python nicht überladbar.

Und was bei mir so ganz grundsätzlich hier schon nicht klappt ist das ``select(Entry)`` weil `Entry` kein Tabellenobjekt ist.

Dann hat `Entry.start` kein `date`-Attribut, was die Ausnahme die das auslöst deutlich rüber bringt:

Code: Alles auswählen

AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object associated with Entry.start has an attribute 'date'
Den Datumsanteil bekommt man in SQL mit der `DATE()`-Funktion. Und ein Datum vergleicht man dann nicht mit einem Tupel mit Zahlen sondern mit einem `datetime.date`-Objekt.

Danach gibt es keine `scalars()`-Methode auf dem `Session`-Objekt.

Dann gibt das keine Ergebnisse weil an dem Datum und mit dem Projekt gar keine Einträge existieren. Weil alle Einträge pro Datum zum gleichen Projekt sind, reicht auch jeweils eines der Kriterien für die Abfrage.

Uhrzeiten kann man nicht einfach voneinander abziehen, oder addieren, denn a) was soll passieren wenn ein Über- oder Unterlauf stattfindet? Und b) hängt das Ergebnis an bestimmten Tagen vom Datum ab. Zum Beispiel bei der Umstellung zwischen Sommer- und Winterzeit. Das wiederum hängt dann auch noch von der Zeitzone ab.

Man kann aber `datetime`-Objekte voneinander abziehen. Das Ergebnis davon ist ein `timedelta`-Objekt, das man nicht auf ganze Zahlen, also auch nicht auf 0 addieren kann.

Sonstige Anmerkungen: `dt`, `dts`, `dtx`, `dtsx` sind keine guten Namen. Selbst hier, für mal eben schnell einen Test, hätte man die doch einfach `start` und `stop` nennen können, wie die Argumente/Attribute zu denen die Werte gehören.

Code: Alles auswählen

#!/usr/bin/env python3
from datetime import date as Date, datetime as DateTime, timedelta as TimeDelta

from sqlalchemy import INTEGER, TEXT, TIMESTAMP, Column, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import func

# DATABASE_NAME = 'project_database.db'
Base = declarative_base()
Session = sessionmaker()


class Entry(Base):
    __tablename__ = "entry"

    id = Column(INTEGER, primary_key=True)
    project = Column(TEXT, nullable=False)
    start = Column(TIMESTAMP, nullable=False, default=DateTime.now)
    stop = Column(TIMESTAMP, nullable=False, default=DateTime.now)


def connect():
    return create_engine("sqlite:///:memory:", echo=True)


def make_session(engine):
    return Session(bind=engine)


def generate_tables(engine):
    Base.metadata.create_all(engine)


def main():
    engine = connect()
    generate_tables(engine)
    session = make_session(engine)

    # Some testing
    for index in range(5):
        one_hour = TimeDelta(hours=1)
        start = DateTime(2022, 5, 12, 5 + index, 5, 14)
        session.add(
            Entry(project="P-21-001", start=start, stop=start + one_hour)
        )
        start += TimeDelta(days=1)
        session.add(
            Entry(project="P-22-001", start=start, stop=start + one_hour)
        )
    session.commit()

    elapsed_time = TimeDelta()
    for entry in (
        session.query(Entry)
        .filter(
            Entry.project == "P-21-001",
            func.date(Entry.start) == Date(2022, 5, 12),
        )
        .all()
    ):
        print(
            f"{entry.project} on {entry.start.date()}"
            f" from {entry.start.time()} to {entry.stop.time()}"
        )
        elapsed_time += entry.stop - entry.start
    print(elapsed_time)


if __name__ == '__main__':
    main()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

@__deets__
Das ist doch eine Klasse, keine Instanz, entsprechend hat start auch garkeinen Wert...
Ich dachte, dass genau hier:

Code: Alles auswählen

 for entry in (
        session.query(Entry)
        .filter(
            Entry.project == "P-21-001",
            func.date(Entry.start) == Date(2022, 5, 12),
        )
        .all()
    ):
...Instanzen erzeugt würden?! Dann habe ich wohl was falsch verstanden.
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Den Datumsanteil bekommt man in SQL mit der `DATE()`-Funktion.
Das ist das etwas verwirrende für mich. Ich dachte, dass ich der Stelle nicht "...in SQL..." Funktionalität bin, sondern in Python-Objekten
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Danach gibt es keine `scalars()`-Methode auf dem `Session`-Objekt.
Die Methode hatte ich aus der Doku, bzw. aus einem Beispiel, in dem die where() Methode benutzt wurde, da ich in (Deinem vorherigen) Beispiel zur query() methode keine where() Methode fand.
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Dann gibt das keine Ergebnisse weil an dem Datum und mit dem Projekt gar keine Einträge existieren. Weil alle Einträge pro Datum zum gleichen Projekt sind, reicht auch jeweils eines der Kriterien für die Abfrage.
Nein. :mrgreen: Ich brauche ja beide Kriterien. Was ich gepostet hatte, war in dem Moment der Versuch, zu schauen, ob es funktioniert. Jedoch war die "Abfrage" dann so, als gäbe es das zweite Kriterium gar nicht, also ignoriert, das war das, was ich testen wollte. Es hätte da keine Ergebnisse geben dürfen, aber es gab eben welche.
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Uhrzeiten kann man nicht einfach voneinander abziehen, oder addieren, denn a) was soll passieren wenn ein Über- oder Unterlauf stattfindet? Und b) hängt das Ergebnis an bestimmten Tagen vom Datum ab. Zum Beispiel bei der Umstellung zwischen Sommer- und Winterzeit. Das wiederum hängt dann auch noch von der Zeitzone ab.
Ok. Wobei das in meinem Fall nie zum Tragen käme. Ich arbeite weder an Mitternacht, noch an der Grenze zur Sommer-/Winderzeit. Die Differenz liegt also immer an einem "normalen" Arbeitstag, im Zeitraum 6:00 Uhr bis 17:00 Uhr.
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Man kann aber `datetime`-Objekte voneinander abziehen. Das Ergebnis davon ist ein `timedelta`-Objekt, das man nicht auf ganze Zahlen, also auch nicht auf 0 addieren kann.
An der Stelle danke für den Code.
__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 11:10 Sonstige Anmerkungen: `dt`, `dts`, `dtx`, `dtsx` sind keine guten Namen. Selbst hier, für mal eben schnell einen Test, hätte man die doch einfach `start` und `stop` nennen können, wie die Argumente/Attribute zu denen die Werte gehören.
Ich wusste, dass das jetzt kommt. :D Aber wo wir beim Thema sind; macht es Sinn, auch "...für eben mal schnell einen Test..." eine Test-Unit zu schreiben, bzw. gibt es bei Python da was spezielles (ähnlich Java)?

Nochmals besten Dank für Eure Unterstützung!!
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

mechanicalStore hat geschrieben: Donnerstag 12. Mai 2022, 12:37 @__deets__
Das ist doch eine Klasse, keine Instanz, entsprechend hat start auch garkeinen Wert...
Ich dachte, dass genau hier:

Code: Alles auswählen

 for entry in (
        session.query(Entry)
        .filter(
            Entry.project == "P-21-001",
            func.date(Entry.start) == Date(2022, 5, 12),
        )
        .all()
    ):
...Instanzen erzeugt würden?!

Erzeugen. Aber die Definition der Spalte fuer die Abfrage ist doch keine Instanz, nix was mit Entry.irgendwas zu tun hat, kann das notwendigerweise sein - du kannst ja auch nicht

Code: Alles auswählen

class Foo:
    def __init__(self):
          self.bar = 10
print(Foo.bar)
machen. Und hast ja selbst schon festgestellt, diese Column-Objekte sind eben Klassen(!)variablen.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

__deets__ hat geschrieben: Donnerstag 12. Mai 2022, 13:03
mechanicalStore hat geschrieben: Donnerstag 12. Mai 2022, 12:37 @__deets__
Das ist doch eine Klasse, keine Instanz, entsprechend hat start auch garkeinen Wert...
Ich dachte, dass genau hier:

Code: Alles auswählen

 for entry in (
        session.query(Entry)
        .filter(
            Entry.project == "P-21-001",
            func.date(Entry.start) == Date(2022, 5, 12),
        )
        .all()
    ):
...Instanzen erzeugt würden?!
Erzeugen. Aber die Definition der Spalte fuer die Abfrage ist doch keine Instanz, nix was mit Entry.irgendwas zu tun hat, kann das notwendigerweise sein - du kannst ja auch nicht

Code: Alles auswählen

class Foo:
    def __init__(self):
          self.bar = 10
print(Foo.bar)
machen. Und hast ja selbst schon festgestellt, diese Column-Objekte sind eben Klassen(!)variablen.
[/quote]
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

__deets__ hat geschrieben: Donnerstag 12. Mai 2022, 13:04
Erzeugen. Aber die Definition der Spalte fuer die Abfrage ist doch keine Instanz, nix was mit Entry.irgendwas zu tun hat, kann das notwendigerweise sein - du kannst ja auch nicht

Code: Alles auswählen

class Foo:
    def __init__(self):
          self.bar = 10
print(Foo.bar)
machen. Und hast ja selbst schon festgestellt, diese Column-Objekte sind eben Klassen(!)variablen.
Hmmm. Ausschnitt aus meinem Buch (stark gekürzt, Prinzip ist gleich unserem Beispiel, ich muss das abtippen):

Code: Alles auswählen

class People(Base):
	__tablename....
	id = Column...
	name = Column...
	count = Column...
	usw
Bespiel-Einträge:
1 Bob 1
2 Jill 20
3 Joe 10
4 Jane 5

Jetzt steht da: "Updates are also fairly straightforward. You retrive the record you want to update, change the values on the mapped instance, and then add the updated record to the session to be written back to the database:

Code: Alles auswählen

jill = session.query(People).filter_by(name='Jill').first()
jill.count = 22
session.add(jill)
session.commit()
Was mich zu der Annahme brachte, ist, dass jill in dem Fall doch eine Instanz sein muss ?!

Und Du selbst schriebst doch auch kürzlich:
Das sind Klassenvariablen weil SA Python hier als "Domain Specific Language" (DSL) "missbraucht". Du deklarierst da etwas, das dann aber zur Laufzeit in eigentliche Klassen umgesetzt wird, und davon dann wiederum Objekte erzeugt werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Ja `jill` ist eine Instanz aber `People` ist keine. Es gibt `jill.name` und `People.name` und das sind unterschiedliche Typen die nicht die gleichen Methoden haben. Bei ``entry = session.query(Entry).filter_by(project="P-21-001").first()`` hat `entry.start` eine `date()`-Methode aber `Entry.start` hat keine `date()`-Methode. Das sind unterschiedliche Attribute mit unterschiedlichen Typen und damit auch nicht zwingend mit den gleichen Methoden. Das eine ist ein `datetime.datetime`-Objekt, das andere ein Attribut das eine Tabellenspalte beschreibt. Und das ist so allgemein gehalten, dass es keine Methoden hat die nur für spezielle SQL-Datentypen gelten. Eine `date()`-Methode würde bei einem VARCHAR ja beispielsweise keinen Sinn ergeben.

Bezüglich der SQL-`DATE()`-Funktion: Man muss bei SQLAlchemy in der Regel kein SQL mehr als Zeichenkette zusammenbasteln, aber letztlich wird/muss ja alles was man in SQLAlchemy ausdrückt auf SQL abgebildet werden, weil die Datenbank das braucht.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 14:13 @mechanicalStore: Ja `jill` ist eine Instanz aber `People` ist keine. Es gibt `jill.name` und `People.name` und das sind unterschiedliche Typen die nicht die gleichen Methoden haben. Bei ``entry = session.query(Entry).filter_by(project="P-21-001").first()`` hat `entry.start` eine `date()`-Methode aber `Entry.start` hat keine `date()`-Methode. Das sind unterschiedliche Attribute mit unterschiedlichen Typen und damit auch nicht zwingend mit den gleichen Methoden. Das eine ist ein `datetime.datetime`-Objekt, das andere ein Attribut das eine Tabellenspalte beschreibt. Und das ist so allgemein gehalten, dass es keine Methoden hat die nur für spezielle SQL-Datentypen gelten. Eine `date()`-Methode würde bei einem VARCHAR ja beispielsweise keinen Sinn ergeben.

Bezüglich der SQL-`DATE()`-Funktion: Man muss bei SQLAlchemy in der Regel kein SQL mehr als Zeichenkette zusammenbasteln, aber letztlich wird/muss ja alles was man in SQLAlchemy ausdrückt auf SQL abgebildet werden, weil die Datenbank das braucht.
Danke für die Erklärung. Genau so hatte ich es auch verstanden. Dann ist offenbar meine Herleitung an anderer Stelle falsch, nämlich dass ich davon ausging, dass:

Code: Alles auswählen

    elapsed_time = TimeDelta()
    for entry in (
        session.query(Entry)
        .filter(
            Entry.project == "P-21-001",
            func.date(Entry.start) == Date(2022, 5, 12),
        )
        .all()
    ):
        print(
            f"{entry.project} on {entry.start.date()}"
            f" from {entry.start.time()} to {entry.stop.time()}"
        )
        elapsed_time += entry.stop - entry.start
... das iterieren

Code: Alles auswählen

for entry ...
doch ebensolche Instanzen erzeugt, (wie jill im anderen Beispiel), Du benutzt ja auch unten

Code: Alles auswählen

elapsed_time += entry.stop - entry.start
und nicht

Code: Alles auswählen

elapsed_time += Entry.stop - Entry.start
Wo ist also dann mein Fehler, dass es heißt, es gäbe keine Instanzen?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ja da wird über Instanzen iteriert, aber Du hast ja nicht `entry.start.date()` versucht sondern in dem `filter()`-Aufruf (bei Dir noch `where()`) hast Du `Entry.start.date()` versucht, und *das* geht halt nicht. Der `filter()`-Aufruf passiert ja vor dem Iterieren und dem erstellen von Instanzen. Der bestimmt ja unter anderem welche Instanzen überhaupt erzeugt werden.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

__blackjack__ hat geschrieben: Donnerstag 12. Mai 2022, 15:18 Ja da wird über Instanzen iteriert, aber Du hast ja nicht `entry.start.date()` versucht sondern in dem `filter()`-Aufruf (bei Dir noch `where()`) hast Du `Entry.start.date()` versucht, und *das* geht halt nicht. Der `filter()`-Aufruf passiert ja vor dem Iterieren und dem erstellen von Instanzen. Der bestimmt ja unter anderem welche Instanzen überhaupt erzeugt werden.
Achsooooo! Es ging nur um den Eintrag im Filter (bzw. where() bei mir), nicht um die Iterationen selbst. Da habe ich das Gelesene komplett fehlinterpretiert. Somit passt alles, vielen Dank.
Dennoch fällt mir gerade in Deinem Filter noch auf:

Code: Alles auswählen

func.date(Entry.start) == Date(2022, 5, 12)
Was bewirkt denn

Code: Alles auswählen

func.date
?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das DATE aufgerufen wird als Funktion, weil es ja ein TIMESTAMP ist, aber du nur auf den Datumsteil pruefen willst. Das Python aequivalent ware "datetime.datetime().date()"
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

__deets__ hat geschrieben: Donnerstag 12. Mai 2022, 16:18 Das DATE aufgerufen wird als Funktion, weil es ja ein TIMESTAMP ist, aber du nur auf den Datumsteil pruefen willst. Das Python aequivalent ware "datetime.datetime().date()"
Ah ok, alles klar. Besten Dank.
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

Moin mal wieder,

Gibt es hierfür was Besseres? Zu Beginn möchte ich kein Stop-Datum eingetragen haben, ein 'None' einzutragen, nach dem ich suchen könnte, funktioniert da aber nicht:

Code: Alles auswählen

STOP_MARKER = DateTime(1970, 1, 1, 0, 0, 0)
...
def finish_project(project, session):
    stop = DateTime.now()
    project_to_close = session.query(Entry).filter(Entry.project == project,
            func.date(Entry.stop) == STOP_MARKER.date()).first()
    if project_to_close:
        project_to_close.stop = stop
        session.add(project_to_close)
        session.commit()
        return True
    else:
        return False
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Für SQL gibt es NULL, was in Python None ist und von SQL-Alchemy unterstützt wird, wenn das DB-Feld nullable ist. Einen magischen Datumswert sollte man nicht verwenden.

Code: Alles auswählen

project_to_close = session.query(Entry).filter(Entry.project == project, Entry.stop == None).first()
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Die `add()`-Methode von Session-Objekten ist dazu da *neue* Objekte der Sitzung bekannt zu machen. Objekte die man von der Datenbank abgefragt hat, kennt das Session-Objekt bereits, weil die Abfrage ja auch über das Session-Objekt lief.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

@Sirius3: Danke. Der Hinweis, dass das Feld Nullable sein können muss, war die Lösung. Daher konnte ich nicht mit None initialisieren. Passt jetzt.
__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 11:53 @mechanicalStore: Die `add()`-Methode von Session-Objekten ist dazu da *neue* Objekte der Sitzung bekannt zu machen. Objekte die man von der Datenbank abgefragt hat, kennt das Session-Objekt bereits, weil die Abfrage ja auch über das Session-Objekt lief.
Hmmm...so steht es in meinem Buch (siehe etwas weiter oben, Beispiel mit Jill). Du meinst also, dass ich add() weglassen kann (aber commit dennoch ausführen muss)?!
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

Nächstes Problem. Ich will das aktuelle Projekt alle 30 Sekunden mit aktueller Zeit in der SnapShot-Tabelle speichern (um sicher zu stellen, bei abruptem Beenden später nach Neustart reagieren zu können, falls das aktuelle Projekt noch keine stop-Zeit besitzt) . Logischerweise in einem extra Thread. Das Ganze fliegt mir aber um die Ohren (ich werfe so viel wie möglich raus):

Code: Alles auswählen

#!/usr/bin/env python3
from datetime import date as Date, datetime as DateTime, timedelta as TimeDelta
from time import sleep

from sqlalchemy import INTEGER, TEXT, TIMESTAMP, Column, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql import func

import threading

c = threading.Condition()

class ThreadSnapShot(threading.Thread):
    def __init__(self, name, active_project, session):
        threading.Thread.__init__(self)
        self.name = name
        self.active_project = active_project
        self.session = session

    def run(self):
        while True:
            c.acquire()
            now = DateTime.now()
            for check_if_exist in (self.session.query(SnapShot).all()):
                self.session.delete(check_if_exist)
                self.session.commit()
            self.session.add(SnapShot(project = self.active_project, last_time = now))
            self.session.commit()

            # Debug
            for entry in (self.session.query(SnapShot)):
                print(
                f"{entry.project} on {entry.last_time.date()}"
                )

            sleep(30)

Base = declarative_base()
Session = sessionmaker()

class Entry(Base):
    __tablename__ = "entry"

    id = Column(INTEGER, primary_key=True)
    project = Column(TEXT, nullable=False)
    start = Column(TIMESTAMP, nullable=False, default=DateTime.now)
    stop = Column(TIMESTAMP, nullable=True, default=None)

class Projects(Base):
    __tablename__ = "projects"

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

class SnapShot(Base):
    __tablename__ = "snapshot"

    id = Column(INTEGER, primary_key=True)
    project = Column(TEXT, nullable=False)
    last_time = Column(TIMESTAMP, nullable=False, default=DateTime.now)

def connect():
    return create_engine("sqlite:///:memory:", echo=False)

def make_session(engine):
    return Session(bind=engine)

def generate_tables(engine):
    Base.metadata.create_all(engine)

def main():
    engine = connect()
    generate_tables(engine) # Only once !
    session = make_session(engine)

    a = ThreadSnapShot('SnapShotThread', 'P-21-001', session)
    a.start()
    sleep(100)
 
if __name__ == '__main__':
    main()
Fehlermeldung ist:

Code: Alles auswählen

sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread. The object was created in 
thread id 13840 and this is thread id 13796.
Ich folgere mal daraus, dass ich die session nicht auf mehrere Threads verteilen kann?! Was wäre hier die Lösung? In neuer session auf die gleiche Datenbank zugreifen?! Lässt sich das ganze in ein extra Modul packen, ohne zirkulare importe zu generieren (hatte ich - aufgrund dessen erfolglos - versucht)? Was könnte ich bei einem Projektwechsel machen? Ein Thread lässt sich ja nur einmal starten. Globale Variablen sind ja ein NoGo.
Zu erwähnen ist auch noch, dass ich vor habe, eine GUI drauf zu packen (TkInter, WX, Qt, etc. .. irgendwas davon), was ja im eigenen Thread läuft.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: GUI läuft nicht in einem eigenen Thread. Einige GUI-Rahmenwerke/Systeme erlauben nicht einmal das die GUI in einem anderen als dem Hauptthread läuft.

Ich würde das dann mit in die GUI-Hauptschleife integrieren, die Rahmenwerke haben eigentlich alle etwas um mindestens zeitverzögert etwas auszuführen, wenn nicht sogar um periodisch etwas auszuführen.

Wozu die extra Tabelle? Warum nicht einfach alle 30 Sekunden die Endzeit des aktuell aktiven Projekts aktualisieren?

Bei `Projects` habe ich den Verdacht, dass weder `Entry.project` noch `SnapShot.project` als TEXT existieren sollte sondern das Fremdschlüssel beziehunsweise `relationship()`\s sein sollten. Also wenn man `SnapShot` überhaupt braucht. `Projects` und der Tabellenname `projects` sollten wahrscheinlich auch eher Einzahl sein, denn das Objekt/ein Datensatz beschreiben ja *ein* Projekt und nicht mehrere.

Wenn sich ein Klassenname als Attributname wiederholt ist das oft ein Zeichen das was komisch ist. Ein Projekt besteht aus einem Projekt? Das Attribut sollte wahrscheinlich eher `name` heissen.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mechanicalStore
User
Beiträge: 91
Registriert: Dienstag 29. Dezember 2009, 00:09

__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 14:31 @mechanicalStore: GUI läuft nicht in einem eigenen Thread. Einige GUI-Rahmenwerke/Systeme erlauben nicht einmal das die GUI in einem anderen als dem Hauptthread läuft.
Ich war der Meinung aufgrund der Doku (vielleicht habe ich das aber auch falch verstanden):

https://docs.python.org/3/library/tkint ... ding-model

Code: Alles auswählen

...
Each Tk object created by tkinter contains a Tcl interpreter. It also keeps track of which thread created that interpreter.
Calls to tkinter can be made from any Python thread. Internally, if a call comes from a thread other than the one that created the Tk object,
an event is posted to the interpreter’s event queue, and when executed, the result is returned to the calling Python thread.
...
__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 14:31 Ich würde das dann mit in die GUI-Hauptschleife integrieren, die Rahmenwerke haben eigentlich alle etwas um mindestens zeitverzögert etwas auszuführen, wenn nicht sogar um periodisch etwas auszuführen.
Theoretisch (bzw später) ja. Aber zuvor wollte ich auf die GUI so lange verzichten, bis alle Funktionalität gegeben ist. Auch zu Lernzwecken. Aber Du hast auch Recht.
__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 14:31 Wozu die extra Tabelle? Warum nicht einfach alle 30 Sekunden die Endzeit des aktuell aktiven Projekts aktualisieren?
Hat den Grund, dass ich im Sonderfall reagieren kann. Bei Nichtbeenden, Rechnerabsturz, etc. wenn also längere Zeit vergangen ist und das Programm neu startet, kann ich immer noch auswählen, ob ich tatsächlich die letzten Stunden an dem Projekt gearbeitet habe, bzw. immer noch daran arbeite, oder meine Zeit in der Fertigung verbracht habe, um was zu prüfen, oder in der IT bei Rechnerabbsturz, usw. Ansonsten wird blindlings davon ausgegangen, dass ich am Projekt gearbeitet habe, was ggf. nicht stimmt. Bzw. kann ich ja auch immer noch daran gearbeitet haben, obwohl der Rechner aus war. Dann stimmt die Endzeit ebenfalls nicht und ich kann nachträglich keine Zeit mehr verbuchen, wenn sie schon verstrichen ist. Manuelle Korrekturen wollte ich vermeiden (wobei sich das sicherlich nicht 100% vermeiden lässt, sodass ich dafür auch noch eine Option einbauen muss). Sobald das Programm neu startet, schaue ich, ob es ein offenes Projekt gibt und ob der Snapshot-Eintrag vorhanden ist, und reagiere entsprechend. Vielleicht denke ich hier aber auch zu umständlich und es geht doch einfacher.
__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 14:31 Bei `Projects` habe ich den Verdacht, dass weder `Entry.project` noch `SnapShot.project` als TEXT existieren sollte sondern das Fremdschlüssel beziehunsweise `relationship()`\s sein sollten. Also wenn man `SnapShot` überhaupt braucht. `Projects` und der Tabellenname `projects` sollten wahrscheinlich auch eher Einzahl sein, denn das Objekt/ein Datensatz beschreiben ja *ein* Projekt und nicht mehrere.
Eigentlich ist nur 'netplan' eindeutig. Ggf. mache ich das zum primary_key. Projects wird später eine Liste (bzw Listbox), nur was da ausgewäht wird, das wird auch in Entry.project eingetragen (bzw. auch in SnapShot.project). Es kann da also nichts inkonsistent werden, weil es keine manuellen Eingaben gibt. Relationship weiß ich nicht, vielleicht überdimensioniert, ob ich nun eine Relation aus Entry auf einen Key in Projects herstelle, oder einfach projects in Entry eintrage, ist doch fast dasselbe. TEXT muss es auf jeden Fall sein, da sich project in verschiedenen Formaten darstellen kann, nie z.B. nur Integer, usw.
__blackjack__ hat geschrieben: Freitag 13. Mai 2022, 14:31 Wenn sich ein Klassenname als Attributname wiederholt ist das oft ein Zeichen das was komisch ist. Ein Projekt besteht aus einem Projekt? Das Attribut sollte wahrscheinlich eher `name` heissen.
Ok, wobei Projects der Plural von project ist.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@mechanicalStore: Der Abschnitt „Threading Model“ ist neu in der Python 3.10 Dokumentation, also in der neuesten (stabilen) Version. Davor stand das da nicht und das war auch definitiv nicht immer so. Und es ist halt auch nur Tk und man kann das nicht einfach grundsätzlich auf alle GUI-Rahmenwerke übertragen.

Die künstliche ID sollte eindeutig sein und man verwendet eigentlich immer künstliche IDs als Schlüssel, auch wenn man andere eindeutige Daten hat. Und den Projektnamen würde ich wahrscheinlich auch eindeutig machen, auch wenn er das streng genommen nicht sein müsste, denn die Erwartungshaltung beim Benutzer ist meistens das etwas mit einem Namen dieses etwas auch eindeutig identifiert.

Es gibt da zwei Perspektiven. Der Rechner, für den ist immer eine ganze Zahl der Schlüssel und das Verknüpungskriterieum. Und keine Texte. Und wenn man Tabellen hat die in Beziehung stehen, dann sollten sie das auch über Fremdschlüssel tatsächlich tun. Und das Schema sollte auch sauber normalisiert sein. Datenbanktabellen sind keine Tabellen im Sinne von „so würde ich das aber in Excel-Tabellen von Hand speichern“. Und die andere Perspektive ist der Benutzer der mit einem Haufen Zahlen nichts anfangen kann, dem man irgendwelche Texte präsentiert anhand derer er die Dinge identifizieren kann.

Eine Klasse beschreibt wie *ein* Objekt aussieht. Für ein Projekt wäre der Name `Project`. Der Name `Projects` würde dem Leser vermitteln, dass es sich nicht um ein Objekt handelt das *ein* Projekt beschreibt, sondern ein Objekt das *mehrere* Projekte beschreibt, also beispielsweise ein Containerobjekt das `Project`-Objekte verwaltet.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Antworten