PyPi / Versionierung modularer Scripts?

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.
grum.py
User
Beiträge: 137
Registriert: Montag 11. Mai 2015, 15:27

DasIch hat geschrieben: Insofern ist major.minor.patch, wobei major rückwärtsinkompatibel ist, minor neue Features bringt und patch alles andere repräsentiert eigentlich dass einzig sinnvolle Schema.
Womit ich wieder das Problem habe, dass neue Definitionsdateien eigentlich neue Features sind und das Skript selbst so ziemlich fertig ist. :K
DasIch hat geschrieben:Übrigens gibt es inzwischen auch Ansätze bei jedem Commit automatisiert ein neues Release zu erzeugen.
Das klingt gut. (Ich committe ja beinahe nur, wenn ich weiß, dass das, was ich da gebaut habe, auch funktioniert.) Gibt es einen Link dazu?
BlackJack

@grum.py: Definitionsdateien sind keine neuen Features. Das sind doch bloss Daten, das Programm ändert sich nicht, seine Funktionalität ändert sich nicht, der Benutzer muss beim Aufruf des Programms gegenüber der Vorgängerversion nichts beachten und hat auch keine neuen Einstellungen oder Kommandozeilenoptionen.

Ein paar Anmerkungen zum Code:

Beim Quelltext fällt als erstes auf das die Einrückung von den üblichen vier Leerzeichen pro Ebene abweicht.

Es gibt in der Standardbibliothek ein `logging`-Modul, da sollte man sich keine eigenen `debuglog()`-Funktionen basteln müssen.

Die `main()` gibt entweder eine Zahl oder `None` zurück, das ist unschön. Und im *Fehlerfall* sollte ein Prozess nicht den Exit-Code 0 zurückgeben, denn das ist der Code für „lief fehlerfrei“.

Ein nacktes ``except:`` ohne eine konkrete Ausnahme ist in der Regel keine vernünftige Ausnahmebehandlung. Damit kann man sich sehr schwer zu findende Fehler einhandeln weil *alle* Ausnahmen gleich behandelt werden, auch solche mit denen man an der Stelle gar nicht gerechnet hat. Ähnliches gilt für `Exception` — das ist sehr schwammig, da sollte man mindestens den kompletten Traceback protokollieren.

Was soll das `h` bei `hConfig` bedeuten? Abkürzungen und kryptische Prä- und Postfixe bei Namen sind nicht so toll.

Die Aufteilung des Codes ist eigenartig. Die Funktionen werden nicht nacheinander aufgerufen sondern es ist eine Aufrufkette so das `parse_lib()` deutlich mehr macht als einfach nur zu Parsen und ein Ergebnis zu liefern sondern auch die Ausgabe aufruft, die wiederum einen Wahrheitswert liefert was man von einer reinen Ausgabefunktion nicht erwarten würde. In der Ausgabefunktion werden auch Entscheidungen getroffen die eigentlich *vor* der Ausgabefunktion stattfinden sollten. Warum heist das erste Argument `thislib`, also was hat das `this` dort zu suchen? Die Funktion gibt auch wieder entweder einen Wahrheitswert oder `None` zurück → komische API. Statt ``matches[0]`` für die Versionsnummer zu nehmen wäre eine benannte Gruppe besser, dann muss man keine Klammern zählen oder darauf achten wo man „non capturing“ Gruppen verwendet. Wobei ich gerade sehe das `findall()` verwendet wird, davon dann immer nur der erste Treffer — womit `findall()` irgendwie unsinnig ist.

Der erste Kommentar in `parse_library()` ist inhaltlich falsch und zwei von drei Kommentaren in ``except``-Zweigen weil die offenbar einfach *kopiert* worden sind und inhaltlich nicht mehr zu der neuen Code-Stelle passen.

Pfadteile sollte man mit `os.path.join()` zusammensetzen. Und den gleichen Pfad sollte man nur einmal zusammensetzen und nicht mehrfach.

``pass`` macht nur in ansonsten leeren Blöcken Sinn. Und an der Stelle sollte ganz bestimmt auch etwas anderes passieren, denn wenn die Webseite nicht geladen werden konnte, dann läuft der nachfolgende Code unweigerlich in einen `NameError` weil `matches` dann nicht definiert ist, was dann von einem wieder sehr groben ``except Exception`` „behandelt“ wird.

Und auch die `parse_library()`-Funktion gibt `True`, `False`, oder `None` zurück. Kann es sein dass das mit den Wahrheitswerten so ein PHP-Ding ist? Eine `print_*()`-Funktion sollte gar nichts zurückgeben und eine `parse_*()`-Funktion sollte das Ergebnis des Parsens zurückgeben. Über Misserfolge informiert man den Aufrufer über Ausnahmen und nicht über einen boole'schen Rückgabewert.
grum.py
User
Beiträge: 137
Registriert: Montag 11. Mai 2015, 15:27

BlackJack hat geschrieben:der Benutzer muss beim Aufruf des Programms gegenüber der Vorgängerversion nichts beachten und hat auch keine neuen Einstellungen oder Kommandozeilenoptionen.
Doch, er kann seine Konfigurationsdatei ggf. um die neu hinzugefügten Bibliotheken erweitern.
BlackJack hat geschrieben:Beim Quelltext fällt als erstes auf das die Einrückung von den üblichen vier Leerzeichen pro Ebene abweicht.
Der Standard war mir neu. Gibt es da einen "Auto-Formatter", um mir solcherlei Fauxpas künftig zu ersparen?
BlackJack hat geschrieben:Es gibt in der Standardbibliothek ein `logging`-Modul, da sollte man sich keine eigenen `debuglog()`-Funktionen basteln müssen.
Die genau das Gleiche tut?
BlackJack hat geschrieben:Und im *Fehlerfall* sollte ein Prozess nicht den Exit-Code 0 zurückgeben, denn das ist der Code für „lief fehlerfrei“.
Danke, ich ändere das mal.
BlackJack hat geschrieben:Ein nacktes ``except:`` ohne eine konkrete Ausnahme ist in der Regel keine vernünftige Ausnahmebehandlung.
Das habe ich (ebenso wie ``Exception``) eigentlich weitgehend eliminiert, es ist nur noch da, wo alles Mögliche auftreten kann, vorhanden.
BlackJack hat geschrieben:Was soll das `h` bei `hConfig` bedeuten?
"handle". Entschuldige - Python ist bei Weitem nicht meine erste Sprache. Ich ändere das auch mal.
BlackJack hat geschrieben:Die Aufteilung des Codes ist eigenartig.
Ist "historisch gewachsen" (guck' lieber nicht ins git-Log)... ;)
BlackJack hat geschrieben:Warum heist das erste Argument `thislib`, also was hat das `this` dort zu suchen?
"Die, die gerade dran ist". Oder verstehe ich meinen eigenen Code falsch?
BlackJack hat geschrieben:Die Funktion gibt auch wieder entweder einen Wahrheitswert oder `None` zurück → komische API.
Ist tatsächlich nicht dafür gemacht, dass sie von Drittanbieterprogrammen mitbenutzt wird. 'tschuldigung! Ich ändere das gleich mit.
BlackJack hat geschrieben:Statt ``matches[0]`` für die Versionsnummer zu nehmen wäre eine benannte Gruppe besser, dann muss man keine Klammern zählen oder darauf achten wo man „non capturing“ Gruppen verwendet. Wobei ich gerade sehe das `findall()` verwendet wird, davon dann immer nur der erste Treffer — womit `findall()` irgendwie unsinnig ist.
Gibt es auch ein einfaches ``find``, das ich dann mit ``match`` füttern könnte?
BlackJack hat geschrieben:Der erste Kommentar in `parse_library()` ist inhaltlich falsch und zwei von drei Kommentaren in ``except``-Zweigen weil die offenbar einfach *kopiert* worden sind und inhaltlich nicht mehr zu der neuen Code-Stelle passen.
Der erste: Naja, eigentlich nicht, die Funktion wird ja in einer Schleife aufgerufen; aber ich ändere den schnell. Die anderen beiden stimmen: Das kann nur passieren, wenn die Definitionsdatei kaputt ist. ;) (Ich gebe zu, sie könnten ausführlicher sein.)
BlackJack hat geschrieben:Pfadteile sollte man mit `os.path.join()` zusammensetzen. Und den gleichen Pfad sollte man nur einmal zusammensetzen und nicht mehrfach.
Du hast Recht, danke.
BlackJack hat geschrieben:``pass`` macht nur in ansonsten leeren Blöcken Sinn.
Ah, ich dachte, damit könnte ich den Fehler als abgefangen kennzeichnen. Was tut es denn?
BlackJack hat geschrieben:Und an der Stelle sollte ganz bestimmt auch etwas anderes passieren, denn wenn die Webseite nicht geladen werden konnte, dann läuft der nachfolgende Code unweigerlich in einen `NameError` weil `matches` dann nicht definiert ist, was dann von einem wieder sehr groben ``except Exception`` „behandelt“ wird.
Ja, ``return False`` wäre vermutlich klüger, danke. Obwohl ...
BlackJack hat geschrieben:Und auch die `parse_library()`-Funktion gibt `True`, `False`, oder `None` zurück.
Gleich nicht mehr. :)
BlackJack hat geschrieben:Kann es sein dass das mit den Wahrheitswerten so ein PHP-Ding ist?
Ja, tatsächlich. Wobei man in PHP ja auf viele "strenge" Sprachkonsturkte gern mal verzichtet. Rückgabewerte? Im Bestfall is' alles "void", wir machen dafür nicht ständig ``try`` und ``catch`` ...
Danke für die Aufklärung. Ich korrigiere das Gröbste gleich. :)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@grum.py: für meinen Geschmack hast Du es mit den Funktionen etwas übertrieben, "print_metadata" oder "print_pkg_info", wobei zweitere auch noch einen falschen Doc-String hat. Natürlich kann man mit logging exakt die selbe Ausgabe bekommen wie Dein debuglog, aber warum sollte man sich die Mühe machen, da die Default-Ausgabe ausreichend ist. Bei print_library_output hattest Du noch nicht gelernt, dass es format gibt, das würde ich nachziehen. Die Funktion sollte auch check_version_and_output_package_information heißen. Ich würde auch kein match-Ergebnis als Parameter übergeben, sondern eine newest_version, dann ist auch am Variablennamen klar, was da gemacht wird. "not <" ist eigentlich ein ">=".
Statt die verschiedenen Paketmanager per copy-paste im Code zu haben, sollten sie als Struktur z.B. in Deiner Config-Datei stehen.
"parse_library" sollte eigentlich "check_library" heißen und läuft immer noch mit einem undefinierten matches weiter. Welche Exception soll denn in "print_library_output" auftreten, die Du versuchst abzufangen? Warum verwendest Du bei config_file io.open und warum heißt das Fileobjekt data_file?
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

grum.py hat geschrieben:Rückgabewerte? Im Bestfall is' alles "void", wir machen dafür nicht ständig ``try`` und ``catch`` ...
Fehlerbehandlung über Rückgabewerte ist doof...

Ein Sinnloses Beispiel:

Code: Alles auswählen

class ExampleException(Exception):
    pass

def fehler():
    raise ExampleException("Hello Exception!")

def foo():
    fehler()

def bar():
    foo()


if __name__ == "__main__":
    try:
        bar()
    except ExampleException as err:
        print("Ein Fehler: %s" % err)
Ausgabe ist dann: Ein Fehler: Hello Exception!

Dabei muß man ja nicht überall ein try...except machen. Das ist ja gerade das Nette daran...

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
BlackJack

@grum.py: Aber neue JavaScript-Bibliotheken auf die Version zu testen sehe ich nicht als neues Feature, das ist immer noch der gleiche Funktionsumfang, nur halt mit mehr Daten. Das Programm selbst und seine Fähigkeiten — beliegige Bibliothekenversionen testen für die es Konfigurationsdateien gibt — ändern sich dadurch nicht. Würdest Du zum Beispiel bei einem Virenscanner sagen das *Programm* hat neue Fähigkeiten jedes mal wenn die Datenbank mit den Virensignaturen aktualisiert wurde? Man drückt doch in der Versionsnummer den Zustand des Codes aus, man will daran ablesen wieviel sich am Programm geändert hat. Und in dem Fall das nur Daten von einer Version zur nächsten aktualisiert werden, hat sich am Programm doch gar nichts geändert. Für mich wäre das eindeutig die letzte Stelle der Versionsnummer.

Es gibt das ``autopep8``-Skript das ein paar Formatierungen anpassen kann damit der Quelltext näher am Style Guide for Python Code ist, unter anderem die Einrückung.

Mit `logging` hat man zwar nicht etwas was *genau* das gleiche tut, aber man kann damit die gleichen Ausgaben bekommen. Man muss aber beispielsweise nicht die `settings` für den „log level“ überall übergeben und ``if``-Abfragen machen, denn bei `logging` legt man das Level einmal beim Logger-Objekt fest und hat im Programm dann einfach überall die Log-Aufrufe ohne Bedingungen. Macht das Programm also etwas einfacher. Ausserdem gibt es eine Methode um eine komplette Ausnahme mit Traceback zu protokollieren.

Okay, `thislib` ist die Bibliothek „die gerade dran ist“ — im Gegensatz zu welcher anderen? Es gibt in der Funktion ja nur `thislib` womit das `this` keine für den Leser wichtige Information trägt. Da hätte ich lieber `library` ausgeschrieben.

Es gibt ein einfaches `re.find()` das aber `re.search()` heisst. ;-)

``# definition file broken?`` steht an drei Stellen, zweimal bei generischen ``except Exception`` wo wirklich alles behandelt wird, also auch Ausnahmen die auftreten können wenn die Datei völlig in Ordnung ist, und die dritte Stelle behandelt einen `urllib2.URLError` der ebenfalls bei einer völlig intakten Datei auftreten kann. Wenn das Netzwerk nicht da ist oder der angefragte Server einen internen Fehler liefert hat das ja nichts mit der Definitionsdatei zu tun.

``pass`` tut gar nichts. Also wirklich überhaupt nichts.
Benutzeravatar
snafu
User
Beiträge: 6741
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

grum.py hat geschrieben:Ich committe ja beinahe nur, wenn ich weiß, dass das, was ich da gebaut habe, auch funktioniert.
Na, das würde ich doch auch sehr empfehlen. Beim Committen neuer Funktionalität muss nicht unbedingt alles perfekt sein. Aber es sollte doch zumindest keinen Fehler werfen, wenn man die neuen Codebereiche anwendet.
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

snafu hat geschrieben:
grum.py hat geschrieben:Ich committe ja beinahe nur, wenn ich weiß, dass das, was ich da gebaut habe, auch funktioniert.
Na, das würde ich doch auch sehr empfehlen. Beim Committen neuer Funktionalität muss nicht unbedingt alles perfekt sein. Aber es sollte doch zumindest keinen Fehler werfen, wenn man die neuen Codebereiche anwendet.
Naja dem würde ich nicht unbedingt zustimmen. Sicherlich möchte man keine Commits in master haben, die unvollständig oder "kaputt" sind aber es kann durchaus Sinn machen in Feature Branches zu arbeiten und dort solche Commits zu machen. Das ist vorallem dann sinnvoll wenn man etwas experimentiert oder im Verlauf des Reviewprozesses noch Änderungswünsche hinzukommen. Dies lässt sich dann vor einem Merge problemlos mit rebase usw. korrigieren.
grum.py
User
Beiträge: 137
Registriert: Montag 11. Mai 2015, 15:27

Sirius3 hat geschrieben:@grum.py: für meinen Geschmack hast Du es mit den Funktionen etwas übertrieben, "print_metadata" oder "print_pkg_info", wobei zweitere auch noch einen falschen Doc-String hat.
Ich habe (noch) nicht so auf externe Einbindbarkeit geachtet, natürlich gehört da einiges noch besser dokumentiert. :K
Sirius3 hat geschrieben:Natürlich kann man mit logging exakt die selbe Ausgabe bekommen wie Dein debuglog, aber warum sollte man sich die Mühe machen, da die Default-Ausgabe ausreichend ist.
Gibt es da eine gute Dokumentation für Unbedarfte wie mich?
Sirius3 hat geschrieben:Bei print_library_output hattest Du noch nicht gelernt, dass es format gibt, das würde ich nachziehen.
Stimmt, danke.
Sirius3 hat geschrieben:Die Funktion sollte auch check_version_and_output_package_information heißen. Ich würde auch kein match-Ergebnis als Parameter übergeben, sondern eine newest_version, dann ist auch am Variablennamen klar, was da gemacht wird.
Ich finde so lange Namen irgendwie unästhetisch. :?
Sirius3 hat geschrieben:"not <" ist eigentlich ein ">=".
Stimmt, danke.
Sirius3 hat geschrieben:Statt die verschiedenen Paketmanager per copy-paste im Code zu haben, sollten sie als Struktur z.B. in Deiner Config-Datei stehen.
Ich würde da gern ein einheitliches Format für die Definitionsdateien erzwingen, und die Configdatei ist ja benutzerabhängig.
Sirius3 hat geschrieben:"parse_library" sollte eigentlich "check_library" heißen und läuft immer noch mit einem undefinierten matches weiter.
Stimmt, danke.
Sirius3 hat geschrieben:Welche Exception soll denn in "print_library_output" auftreten, die Du versuchst abzufangen?
Ähm, inzwischen keine mehr. :oops:
Sirius3 hat geschrieben:Warum verwendest Du bei config_file io.open
Was denn sonst?
Sirius3 hat geschrieben:und warum heißt das Fileobjekt data_file?
Wie denn sonst?
grum.py
User
Beiträge: 137
Registriert: Montag 11. Mai 2015, 15:27

BlackJack hat geschrieben:Aber neue JavaScript-Bibliotheken auf die Version zu testen sehe ich nicht als neues Feature, das ist immer noch der gleiche Funktionsumfang, nur halt mit mehr Daten. Das Programm selbst und seine Fähigkeiten — beliegige Bibliothekenversionen testen für die es Konfigurationsdateien gibt — ändern sich dadurch nicht. Würdest Du zum Beispiel bei einem Virenscanner sagen das *Programm* hat neue Fähigkeiten jedes mal wenn die Datenbank mit den Virensignaturen aktualisiert wurde?
Wenn ein Virenscanner neue Signaturen bekommt, die man nutzen möchte, muss man nichts ändern.

Wenn mein Skript neue Definitionen bekommt, die man nutzen möchte, muss man die Konfiguration ändern.

Darum ist der Vergleich vielleicht nicht ganz richtig.
BlackJack hat geschrieben:Es gibt das ``autopep8``-Skript das ein paar Formatierungen anpassen kann damit der Quelltext näher am Style Guide for Python Code ist, unter anderem die Einrückung.
Danke, scheint zu funktionieren.
BlackJack hat geschrieben:Okay, `thislib` ist die Bibliothek „die gerade dran ist“ — im Gegensatz zu welcher anderen? Es gibt in der Funktion ja nur `thislib` womit das `this` keine für den Leser wichtige Information trägt. Da hätte ich lieber `library` ausgeschrieben.
Ja - das war alles mal Teil von main(), darum ... :)
BlackJack hat geschrieben:Es gibt ein einfaches `re.find()` das aber `re.search()` heisst. ;-)
Danke. Das nutz' ich mal.
BlackJack hat geschrieben:``# definition file broken?`` steht an drei Stellen, zweimal bei generischen ``except Exception`` wo wirklich alles behandelt wird, also auch Ausnahmen die auftreten können wenn die Datei völlig in Ordnung ist, und die dritte Stelle behandelt einen `urllib2.URLError` der ebenfalls bei einer völlig intakten Datei auftreten kann. Wenn das Netzwerk nicht da ist oder der angefragte Server einen internen Fehler liefert hat das ja nichts mit der Definitionsdatei zu tun.
Darum das Fragezeichen. ;-)
BlackJack hat geschrieben:``pass`` tut gar nichts. Also wirklich überhaupt nichts.
Warum gibt es das dann?
BlackJack

@grum.py: ``pass`` gibt es weil aus syntaktischen Gründen in jedem Block etwas stehen muss. Wenn man also in einem Block „nichts“ stehen haben möchte, kann dort ``pass`` verwenden. Mehr oder weniger sinnfreies Beispiel:

Code: Alles auswählen

    for thing in things:
        try:
            value = int(thing)
            break
        except ValueError:
            pass  # Intentionally ignored.
    else:
        raise ValueError('no thing could be converted to int')

    do_something_with(value)
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@grum.py: pass braucht man halt dann, wenn man explizit nichts machen will. Das logging-Modul hat eine Dokumentation, inklusive Beispielen. Wenn Du die Paketmanager nicht in eine Config-Datei packen willst, so doch wenigstens als Konstante an den Anfang des Skripts. Dort gehören auch die Default-Settings hin.

Die Konfigurations-Datei muß man doch nur ändern, wenn man selbst neue Bibliotheken verwendet, also ist das ja unabhängig von der Revisions-Nummer.
grum.py
User
Beiträge: 137
Registriert: Montag 11. Mai 2015, 15:27

Wenn man welche reinschreibt, die das Skript noch nicht kennt, dann gibt es aber eine Fehlermeldung, weshalb man das vielleicht nicht so oft macht. :K

Danke für die Erklärungen zu "pass". :)
Antworten