mittels LibreOffice ein Python-Script aufzurufen - erneute Frage

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.
Antworten
Stephan_2021
User
Beiträge: 52
Registriert: Sonntag 11. Juli 2021, 09:43

Hallo,

aufgrund der erhaltenen Antworten in:
viewtopic.php?f=1&t=52522

habe ich folgendes Python-Script erstellt:

Code: Alles auswählen

import mysql.connector as mc

def griddata(phost,pport,puser,ppassword,pdatabase,psql):
 connection = mc.connect(host = phost,
					port = pport,
					user = puser,
					password = ppassword,
					database = pdatabase)
				
 cursor = connection.cursor()
 cursor.execute(psql)
		
 result = cursor.fetchall()

 results = [tuple(str(item) for item in t) for t in result]

 cursor.close()
 connection.close()
 return results


und kann es in LibreOffice so aufrufen:

Code: Alles auswählen

Dim oScript, RS_Python, oMasterScriptProviderFactory, g_MasterScriptProvider
	
oMasterScriptProviderFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory")
g_MasterScriptProvider = oMasterScriptProviderFactory.createScriptProvider("")
oScript = g_MasterScriptProvider.getScript("vnd.sun.star.script:mysqlgetdata.py$griddata?language=Python&location=user")
RS_Python = oScript.invoke(Array(DBHost,DBPort,DBUser,DBPass,NameDBInMySQL,"SELECT * from tabelle14 LIMIT 5000"),Array(),Array())

Ich habe davon ausgehend nun folgendes Script erstellt:

Code: Alles auswählen

import mysql.connector as mc

def gridcolumns(phost,pport,puser,ppassword,pdatabase,psql):
 connection = mc.connect(host = phost,
					port = pport,
					user = puser,
					password = ppassword,
					database = pdatabase)
				
 cursor = connection.cursor()
 cursor.execute(psql)
 columns = cursor.column_names
 
 cursor.close()
 connection.close()
 
 return columns

Welches aber beim Aufruf mit:

Code: Alles auswählen

oMasterScriptProviderFactory = createUnoService("com.sun.star.script.provider.MasterScriptProviderFactory")
g_MasterScriptProvider = oMasterScriptProviderFactory.createScriptProvider("")
oScript = g_MasterScriptProvider.getScript("vnd.sun.star.script:mysqlgetcolumns.py$gridcolumns?language=Python&location=user")
RS_Python = oScript.invoke(Array(DBHost,DBPort,DBUser,DBPass,NameDBInMySQL,"SELECT * from tabelle14 LIMIT 1"),Array(),Array())
Msgbox RS_Python
folgende Fehlermeldung (Innerhalb LibreOffice) liefert:

"Es ist eine Ausnahme aufgetreten
Type: com.sun.star.uno.RuntimeException
Message: Error during invoking function gridcolumns in module file:///E:/LO_603/LibreOfficePortable/Data/settings/user/Scripts/python/mysqlgetcolumns.py (<class 'mysql.connector.errors.InternalError'>: Unread result found
File "E:\LO_603\LibreOfficePortable\App\libreoffice\program\pythonscript.py", line 875, in invoke
ret = self.func( *args )
File "E:\LO_603\LibreOfficePortable\Data\settings\user\Scripts\python\mysqlgetcolumns.py", line 14, in gridcolumns
cursor.close()
File "E:\LO_603\LibreOfficePortable\App\libreoffice\program\python-core-3.5.4\lib\mysql\connector\cursor.py", line 397, in close
self._connection.handle_unread_result()
File "E:\LO_603\LibreOfficePortable\App\libreoffice\program\python-core-3.5.4\lib\mysql\connector\connection.py", line 1277, in handle_unread_result
raise errors.InternalError("Unread result found")
). (1)"




1. Was ist falsch?

2. Wie kann ich in einem zweiten Schritt beide Python-Scripte in eine *.py-Datei schreiben, so das ich sowohl "griddata" wie auch "gridcolumns" ansprechen kann?




Gruß
Stephan
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Die Fehlermeldung ist doch sehr sprechend: er hat ungelesene Ergebnisse gefunden. Was ja auch stimmt. Du liest nichts aus der Abfrage. Das ist ja auch einer der Unterschiede. Lies also die Ergebnisse, und dann kannst du erst column_names aufrufen.

Wenn du beides in einer Funktion machen willst, liefer einfach ein Tupel aus Spaltennamen und Ergebnissen zurück.
Stephan_2021
User
Beiträge: 52
Registriert: Sonntag 11. Juli 2021, 09:43

Lies also die Ergebnisse
Wie mache ich das?
Wenn du beides in einer Funktion machen willst, liefer einfach ein Tupel aus Spaltennamen und Ergebnissen zurück.
Nein, ich will nicht Beides in einer Funktion machen. Oder verstehe ich hierbei den Begriff "Funktion" falsch?

Was ich möchte ist eine *.py-Datei, die ich entweder aufrufen kann mit:

oScript = g_MasterScriptProvider.getScript("vnd.sun.star.script:meinScript.py$griddata?language=Python&location=user")
RS_Python = oScript.invoke(Array(DBHost,DBPort,DBUser,DBPass,NameDBInMySQL,"SELECT * from tabelle14 LIMIT 5000"),Array(),Array())

oder mit:

oScript = g_MasterScriptProvider.getScript("vnd.sun.star.script:meinScript.py$gridcolumns?language=Python&location=user")
RS_Python = oScript.invoke(Array(DBHost,DBPort,DBUser,DBPass,NameDBInMySQL,"SELECT * from tabelle14 LIMIT 1"),Array(),Array())


und die jeweils ein anderes ergebnis liefert, einmal "return columns" und einmal "return results". Oder geht das SO nicht?


Gruß
Stephan
Stephan_2021
User
Beiträge: 52
Registriert: Sonntag 11. Juli 2021, 09:43

ich habe jetzt folgendes (Funktionierendes) Script erstellt:

Code: Alles auswählen

import mysql.connector as mc

def griddata(phost,pport,puser,ppassword,pdatabase,psql,prueck):
 connection = mc.connect(host = phost,
					port = pport,
					user = puser,
					password = ppassword,
					database = pdatabase)
				
 cursor = connection.cursor()
 cursor.execute(psql)
		
 result = cursor.fetchall()
 columns = cursor.column_names

 results = [tuple(str(item) for item in t) for t in result]

 cursor.close()
 connection.close()
 if prueck == 'Daten':
  return results
 elif prueck == 'Spaltennamen':
  return str(columns)

Ist das "schönes" Python?

Gibt es einen Weg das stattdessen mit zwei "def" zu implementieren (ohne den ZUsatzparameter "prueck")?


Gruß
Stephan
Benutzeravatar
__blackjack__
User
Beiträge: 13006
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Stephan_2021: Ich würde sagen das ist kein schönes Python. Zum ersten ist eine Einrücktiefe von *einem* Leerzeichen wirklich schlecht erkennbar. Konvention sind vier Leerzeichen pro Ebene.

Argumente die entscheiden dass/ob semantisch vollig unterschiedliche Werte zurückgegeben werden sind unschön. Genau wie der komische Funktionsname der überhaupt nicht verrät was die Funktion *tut*. Funktionen sind üblicherweise nach der Tätigkeit benannt, die sie durchführen. `griddata()` ist keine Tätigkeit sondern ein ”Ding”, also ein Name den man eher passiven Werten gibt. Mit dem Argument welches entscheidet was denn die Funktion liefert wird es eventuell auch schwierig *einen* passenden Namen für die Funktion zu finden die wahlweise mehr als eine Sache tut.

Namen sollten keine kryptischen Zusätze haben. Was sollen die ganzen `p` bei den Argumentnamen bedeuten? Entweder schreibt man das aus, oder man lässt es weg. Wenn die alle die gleiche Bedeutung haben, dann gehört diese Information vielleicht auch eher in den Funktionsnamen.

`rueck` ist ein komischer Name der mir so nichts sagt. Ist auch plötzlich Deutsch unter ansonsten englischsprachigen Bezeichnern.

`result` vs. `results` ist unverständlich. Beides sind eigentlich die gleichen `results` — Mehrzahl — in unterschiedlicher Darstellung.

Bei ``if``/``elif`` ohne abschliessendes ``else`` sollte man immer etwas Misstrauisch sein. Das kann sinnvoll sein, aber hier führt es beispielsweise dazu das bei einem falschen `prueck`-Wert die Funktion implizit einfach `None` zurück gibt, statt beispielsweise eine Ausnahme auszulösen.

Das schliessen von DB-Verbindung und Cursor würde ich besser gegen Ausnahmen absichern.

Zwischenstand (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
from contextlib import closing

from mysql.connector import connect


def query(host, port, user, password, database, sql, what):
    with closing(
        connect(
            host=host,
            port=port,
            user=user,
            password=password,
            database=database,
        )
    ) as connection:
        with connection.cursor():
            cursor = connection.cursor()
            cursor.execute(sql)
            column_names = cursor.column_names
            results = [tuple(map(str, row)) for row in cursor.fetchall()]

    if what == "Daten":
        return results
    elif what == "Spaltennamen":
        return str(column_names)  # XXX `str()` representation of sequence?
    else:
        raise ValueError(f"illegal `what` value {what!r}")
Was hier komisch bis falsch aussieht ist das Werte wie Listen in Zeichenketten umgewandelt werden. Mit der Zeichenkettendarstellung der Spaltennamen*struktur* kann und sollte man eigentlich nichts anfangen, ausser zur Ausgabe zur Fehlersuche beispielsweise. Das ist weder für den normalen Benutzer zum lesen, und noch weniger zur Weiterverarbeitung gedacht oder wirklich geeignet.

Ansonsten würde ich daraus entweder zwei Funktionen machen, eventuell mit einer dritten, Nicht-öffentlichen, die den gemeinsamen Teil enthält, oder einfach eine Funktion, die *beide* Werte als Tupel liefern. Der Aufrufer kann dann den Teil den er nicht benötigt, einfach ignorieren.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wieso willst du das nicht in einem machen? Die DB muss die Arbeit sonst zweimal machen.

Und in deinem LibreOffice Script steht doch, wie man den funktionsnamen der Datei benennt - $gridcolumns ist doch der Name der Funktion.
Stephan_2021
User
Beiträge: 52
Registriert: Sonntag 11. Juli 2021, 09:43

Deine Kritik ist ja 'vernichtend', auch wenn ich fast alles daran verstehen kann. Meine Frage betreffs "schön" zielte eigentlich darauf ob ein Code der Form:

Code: Alles auswählen

def ...
...
def ...
...
hier nicht vielleicht 'schöner' wäre als ein Code der Form:

Code: Alles auswählen

def ...
if ...
  ...
 elif
  ...
Mein Problem war das, zum Zeitpunkt als ich meine Frage stellte, ich Probleme hatte zwei def in einer Datei unterzubringen, weil mein Editor (Notepad++) das alles mit einer durchgehenden Linie markierte, weshalb mir klar war es muss irgendein Schreibfehler vorliegen, nur ich wusste nicht was konkret.
Nach 'dutzendfach' draufschauen fand ich dann als Fehler ein Leerzeichen VOR dem zweiten def ... als Programmierer diverser Basic-Dialekte fällt es mir leider schwer bei Python immer konzentriert auf die richtige Codeeinrückung zu achten.

Code: Alles auswählen

Was sollen die ganzen `p` bei den Argumentnamen bedeuten?
Als Phython-Anfänger war das nur meine Absicherung dagegen das etwas wie "host=host" Probleme machen könnte (wäre das linke "host" nur eine Variable, hätte mir das weniger Sorgen gemacht, nur es ist doch innerhabe des .connect() eine Parameter und in diesem Sinne vielleicht ein reserviertes Wort)
Was hier komisch bis falsch aussieht ist das Werte wie Listen in Zeichenketten umgewandelt werden. Mit der Zeichenkettendarstellung der Spaltennamen*struktur* kann und sollte man eigentlich nichts anfangen, ausser zur Ausgabe zur Fehlersuche beispielsweise.
Gemeint ist das "return str(columns)"?
Wenn ja, so ist das momentan nur zur Anzeige gedacht, im fertigen Code soll dort "return columns" stehen

Oder ist AUCH "[tuple(str(item) for item in t) for t in result]" gemeint?


Ich habe jetzt, unter Berücksichtigung Deiner Kritik eine Variante geschrieben, die ich (fast komplett) verstehen kann und die zunächst funktioniert:

Code: Alles auswählen

import mysql.connector as mc

def get_data_for_grid(host, port, user, password, database, sql):
    connection = mc.connect(host = host,
        port = port,
        user = user,
        password = password,
        database = database)

    cursor = connection.cursor()
    cursor.execute(sql)

    resultraw = cursor.fetchall()
    result = [tuple(str(item) for item in t) for t in resultraw]
    cursor.close()
    connection.close()

    return result

def get_columnnames(host, port, user, password, database, sql):
    connection = mc.connect(host = host,
        port = port,
        user = user,
        password = password,
        database = database)

    cursor = connection.cursor()
    cursor.execute(sql)

    resultraw = cursor.fetchall()
    columns = cursor.column_names
    cursor.close()
    connection.close()
    
    return str(columns)



Fragen/Bitten an Dich wären:

1.
Das schliessen von DB-Verbindung und Cursor würde ich besser gegen Ausnahmen absichern.
ich sehe was in DEinem Codebeispiel mutmaßlich dieses Thema betrifft. Kannst Du das erläutern oder anders schreiben so das ich es verstehen kann?

2.
Wie muss ich meinen Code umschreiben um nicht den Teil der die Verbindung zur DB herstellt, doppelt zu schreiben?

3.
Ich frage gleich einmal auch nach:

result = [tuple(str(item) for item in t) for t in resultraw]

Ich habe den Schnipsel irgendwo im Internet gefunden und einzig weiß ich das er (ungefähr) tut was ich wilkl, dennn "result" hat anschliessend die richtige Form das ich es, in LibreOffice, direkt in ein Grid schreiben kann (http://www.openoffice.org/api/docs/comm ... ml#addRows). DAS ist momentan der Hauptgrund mich mit Python zu beschäftigen, denn in StarBasic ist die erzeugung des "result" ein sehr zeitäufwändige Sache bei großen Datenmengen und praktisch kaum verwendbar, weil die Aufbereitung viele viele Sekunden dauert die der Anwender auf die Dialoganzeige warten muss.

Allerdings habe ich bisher nur mit DB-Feldern des Typs Text (bzw. VarChar) und Dezimal getestet und result enthält die Texte richtig, aber das Dezimal fälschlich mit "." statt ",".

Kannst Du mir da etwas auf die Sprünge helfen? Der fehlerhafte "." ist Eines, aber ich muss auch durchackern wie sich andere DB-Feldtypen 'verhalten' und weiß noch garnicht auf welche Probleme ich stossen werde.
Vielleicht hast Du einen Link dazu?
Ich muss andererseits aber gestehen, ich habe mich bisher noch garnicht mit DIESER Codezeile beschäftigt, vielleicht komme ich auch alleine voran (und wüdrde dann ggf. nochmals einen Thread DAZU eröffnen).



Gruß
Stephan
Stephan_2021
User
Beiträge: 52
Registriert: Sonntag 11. Juli 2021, 09:43

Code: Alles auswählen

Wieso willst du das nicht in einem machen?
Wenn ich die Frage richtig verstehe, dann will ich das deshalb nicht "in einem" machen, weil ich bisher nicht weiß wie ich das in einem übergeben kann, aber auch weil ich vermute die Übergabe müsste als ein Array erfolgen und das wäre mir deshalb nicht recht, weil ich beim 'rumspielen' bereits gemerkt habe das das Nur-Abfragen der Spaltennamen schnell geht, aber das Abfragen der Daten doch etwas Zeit braucht.
Da ich aber in bestimmten Situationen wirklich nur die Spaltennamen brauche, will ich mir die Verzögerung der zusätzlichen Datenabfrage sparen.

"Verzögerung"?
Ohne die Zeit gestoppt zu jaben braucht bei meiner DB (plus konkreter Hardware/Software-Umgebung) die Abfrage von 5000 Datensätzen ca. 1 Sekunde, 20.000 brauchen aber schon ca. 5 Sekunden


Gruß
Stephan
Antworten