Verbindungsaufbau nur wenn nötig
This commit is contained in:
parent
e7fe3fb037
commit
f5a4342bcf
@ -46,3 +46,9 @@ Pandas / SqlAlchemy / xlsxwriter
|
|||||||
Sollen Excel-Dateien mit Pandas erzeugt, werden, so muss Pandas, SqlAlchemy und xlsxwriter installiert sein
|
Sollen Excel-Dateien mit Pandas erzeugt, werden, so muss Pandas, SqlAlchemy und xlsxwriter installiert sein
|
||||||
(`python -m pip install pandas sqlalchemy xlsxwriter`).
|
(`python -m pip install pandas sqlalchemy xlsxwriter`).
|
||||||
|
|
||||||
|
|
||||||
|
PySimpleGUI und andere
|
||||||
|
----------------------
|
||||||
|
Einige Beispiele benutzen PySimpleGUI (``python -m pip install pysimplegui``)
|
||||||
|
sowie teilweise spezielle Bibliotheken etwa zum Pretty-Printing von SQL (``python -m pip install sqlparse sqlfmt``). Dies
|
||||||
|
sind aber Abhängigkeiten von Beispielen, nicht der Bibliothek selbst.
|
||||||
|
@ -4,10 +4,11 @@ typische Anwendungsfälle
|
|||||||
einfache Admin-Aufgaben
|
einfache Admin-Aufgaben
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
Selten auftretende Admin-Aufgaben lassen sich gut mittels Python-Scripten automatisieren.
|
Selten auftretende Admin-Aufgaben lassen sich gut mittels Python-Scripten
|
||||||
Es ist sehr einfach möglich, auf die DB, aber auch auf SOAP-Schnittstelle zuzugreifen.
|
automatisieren. Es ist sehr einfach möglich, auf die DB, aber auch auf
|
||||||
Ich habe dies vor allem für Wartungsarbeiten an Anpassungstabellen, die für eigene Erweiterungen
|
SOAP-Schnittstelle der APP-Serverse zuzugreifen. Zudem ist rudimentärer Zugriff
|
||||||
entwickelt wurden, genutzt.
|
auf ASMX-Seiten implementiert. Ich habe dies vor allem für Wartungsarbeiten an
|
||||||
|
Anpassungstabellen genutzt, die für eigene Erweiterungen entwickelt wurden.
|
||||||
|
|
||||||
Als triviales Beispiel sucht folgender Code alle `DOCUMENTS` Einträge in
|
Als triviales Beispiel sucht folgender Code alle `DOCUMENTS` Einträge in
|
||||||
Artikeln (angezeigt als `Bild` in `ArtikelRec.aspx`), für die Datei, auf die
|
Artikeln (angezeigt als `Bild` in `ArtikelRec.aspx`), für die Datei, auf die
|
||||||
@ -88,7 +89,7 @@ Dank der Bibliothek `zeep` ist es auch sehr einfach möglich, auf beliebige SOAP
|
|||||||
Beispielsweise kann auf die Sys-Config auch händisch, d.h. durch direkten Aufruf einer SOAP-Methode
|
Beispielsweise kann auf die Sys-Config auch händisch, d.h. durch direkten Aufruf einer SOAP-Methode
|
||||||
des APP-Servers zugegriffen werden::
|
des APP-Servers zugegriffen werden::
|
||||||
|
|
||||||
client = server.server_conn.getAppClient("p2system", "SysConf");
|
client = server.getAppClient("p2system", "SysConf");
|
||||||
print (client.service.getString("STAMM", "MYLAND"))
|
print (client.service.getString("STAMM", "MYLAND"))
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,8 +14,8 @@ sys.path.append('../src/')
|
|||||||
project = 'PyAPplus64'
|
project = 'PyAPplus64'
|
||||||
copyright = '2023, Thomas Tuerk'
|
copyright = '2023, Thomas Tuerk'
|
||||||
author = 'Thomas Tuerk'
|
author = 'Thomas Tuerk'
|
||||||
version = '1.1.1'
|
version = '1.1.2'
|
||||||
release = '1.1.1'
|
release = '1.1.2'
|
||||||
|
|
||||||
# -- General configuration ---------------------------------------------------
|
# -- General configuration ---------------------------------------------------
|
||||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||||
|
@ -18,7 +18,8 @@ das Deploy-, das Test- und das Prod-System. Ein Beispiel ist im Unterverzeichnis
|
|||||||
|
|
||||||
Damit nicht in jedem Script immer wieder neu die Konfig-Dateien ausgewählt werden müssen, werden die Konfigs für
|
Damit nicht in jedem Script immer wieder neu die Konfig-Dateien ausgewählt werden müssen, werden die Konfigs für
|
||||||
das Prod-, Test- und Deploy-System in ``examples/applus_configs.py`` hinterlegt. Diese Datei wird in allen Scripten importiert,
|
das Prod-, Test- und Deploy-System in ``examples/applus_configs.py`` hinterlegt. Diese Datei wird in allen Scripten importiert,
|
||||||
so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Verfügung stehen.
|
so dass das Config-Verzeichnis und die darin enthaltenen Configs einfach zur Verfügung stehen. Zudem werden in dieser Datei auch alle verwendeten
|
||||||
|
Kombinationen aus System und Umgebung hinterlegt. So kann in Scripten auch eine Auswahl des Systems implementiert werden.
|
||||||
|
|
||||||
.. literalinclude:: ../../examples/applus_configs.py
|
.. literalinclude:: ../../examples/applus_configs.py
|
||||||
:language: python
|
:language: python
|
||||||
@ -74,6 +75,44 @@ Die GUI wird um die Erzeugung von Excel-Dateien mit Mengenabweichungen gebaut.
|
|||||||
:lines: 9-
|
:lines: 9-
|
||||||
:linenos:
|
:linenos:
|
||||||
|
|
||||||
|
``complete_sql.pyw``
|
||||||
|
--------------------
|
||||||
|
Beispiel, wie ein einfacher APP-Server Aufruf über eine GUI zur Verfügung gestellt und mittels
|
||||||
|
Python-Bibliotheken erweitert werden kann. Zudem wird demonstriert, wie eine Auswahl verschiedenere
|
||||||
|
Systeme und Umgebungen realisiert werden kann.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/complete_sql.pyw
|
||||||
|
:language: python
|
||||||
|
:lines: 9-
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
``importViewUDF.py``
|
||||||
|
--------------------
|
||||||
|
Folgende Scripte erlauben den einfachen Import von DB-Anpass-Dateien, Views und UDFs über den Windows-Explorer.
|
||||||
|
Werden Verknüpfungen zu den Scripten ``importViewUDFDeploy.pyw`` und ``importViewUDFTest.pyw`` in ``%appdata%\Microsoft\Windows\SendTo`` abgelegt,
|
||||||
|
so können eine oder mehrerer solcher Dateien mittels _Kontextmenü (Rechtsklick) - Senden an_ an APplus zur Verarbeitung übergeben werden.
|
||||||
|
Dabei ist es wichtig, dass sich die Dateien im für den jeweiligen Typ passenden Verzeichnis befinden.
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/importViewUDF.py
|
||||||
|
:language: python
|
||||||
|
:lines: 9-
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Wrapper für Deploy-System:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/importViewUDFDeploy.pyw
|
||||||
|
:language: python
|
||||||
|
:lines: 9-
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
Wrapper für Test-System:
|
||||||
|
|
||||||
|
.. literalinclude:: ../../examples/importViewUDFTest.pyw
|
||||||
|
:language: python
|
||||||
|
:lines: 9-
|
||||||
|
:linenos:
|
||||||
|
|
||||||
|
|
||||||
``copy_artikel.py``
|
``copy_artikel.py``
|
||||||
-----------------------
|
-----------------------
|
||||||
Beispiel, wie Artikel inklusive Arbeitsplan und Stückliste dupliziert werden kann.
|
Beispiel, wie Artikel inklusive Arbeitsplan und Stückliste dupliziert werden kann.
|
||||||
|
@ -7,10 +7,26 @@
|
|||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
from PyAPplus64.applus import APplusServerConfigDescription
|
||||||
|
|
||||||
basedir = pathlib.Path(__file__)
|
basedir = basedir = pathlib.Path(__file__) # Adapt to your needs
|
||||||
configdir = basedir.joinpath("config")
|
configdir = basedir.joinpath("config")
|
||||||
|
|
||||||
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
serverConfYamlDeploy = configdir.joinpath("applus-server-deploy.yaml")
|
||||||
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
serverConfYamlTest = configdir.joinpath("applus-server-test.yaml")
|
||||||
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
serverConfYamlProd = configdir.joinpath("applus-server-prod.yaml")
|
||||||
|
|
||||||
|
|
||||||
|
serverConfDescProdEnv1 = APplusServerConfigDescription("Prod/Env1", serverConfYamlProd, env="Env1")
|
||||||
|
serverConfDescProdEnv2 = APplusServerConfigDescription("Prod/Env2", serverConfYamlProd, env="Env2")
|
||||||
|
serverConfDescTestEnv1 = APplusServerConfigDescription("Test/Env1", serverConfYamlTest, env="Env1")
|
||||||
|
serverConfDescTestEnv2 = APplusServerConfigDescription("Test/Env2", serverConfYamlTest, env="Env2")
|
||||||
|
serverConfDescDeploy = APplusServerConfigDescription("Deploy", serverConfYamlDeploy)
|
||||||
|
|
||||||
|
serverConfDescs = [
|
||||||
|
serverConfDescProdEnv1,
|
||||||
|
serverConfDescProdEnv2,
|
||||||
|
serverConfDescTestEnv1,
|
||||||
|
serverConfDescTestEnv2,
|
||||||
|
serverConfDescDeploy
|
||||||
|
]
|
105
examples/complete_sql.pyw
Normal file
105
examples/complete_sql.pyw
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||||
|
#
|
||||||
|
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||||
|
#
|
||||||
|
# Use of this source code is governed by an MIT-style
|
||||||
|
# license that can be found in the LICENSE file or at
|
||||||
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import PySimpleGUI as sg # type: ignore
|
||||||
|
import PyAPplus64
|
||||||
|
import applus_configs
|
||||||
|
import pathlib
|
||||||
|
from typing import Tuple, Optional, Union
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sqlparse
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
import sqlfmt.api
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def prettyPrintSQL(format, sql):
|
||||||
|
try:
|
||||||
|
if format == "sqlfmt":
|
||||||
|
mode = sqlfmt.api.Mode(dialect_name="ClickHouse")
|
||||||
|
sqlPretty = sqlfmt.api.format_string(sql, mode)
|
||||||
|
return sqlPretty.replace("N '", "N'") # fix String Constants
|
||||||
|
elif format == "sqlparse-2":
|
||||||
|
return sqlparse.format(sql, reindent=True, keyword_case='upper')
|
||||||
|
elif format == "sqlparse":
|
||||||
|
return sqlparse.format(sql, reindent_aligned=True, keyword_case='upper')
|
||||||
|
else:
|
||||||
|
return sql
|
||||||
|
except e:
|
||||||
|
print (str(e))
|
||||||
|
return sql
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
monospaceFont = ("Courier New", 12)
|
||||||
|
sysenvs = applus_configs.serverConfDescs[:];
|
||||||
|
sysenvs.append("-");
|
||||||
|
layout = [
|
||||||
|
[sg.Button("Vervollständigen"), sg.Button("aus Clipboard", key="import"), sg.Button("nach Clipboard", key="export"), sg.Button("zurücksetzen", key="clear"), sg.Button("Beenden"),
|
||||||
|
sg.Text('System/Umgebung:'), sg.Combo(sysenvs, default_value="-", key="sysenv", readonly=True), sg.Text('Formatierung:'), sg.Combo(["-", "sqlfmt", "sqlparse", "sqlparse-2"], default_value="sqlparse", key="formatieren", readonly=True)
|
||||||
|
],
|
||||||
|
[sg.Text('Eingabe-SQL')],
|
||||||
|
[sg.Multiline(key='input', size=(150, 20), font=monospaceFont)],
|
||||||
|
[sg.Text('Ausgabe-SQL')],
|
||||||
|
[sg.Multiline(key='output', size=(150, 20), font=monospaceFont, horizontal_scroll=True)]
|
||||||
|
]
|
||||||
|
|
||||||
|
# server = PyAPplus64.applusFromConfigFile(confFile, user=user, env=env)
|
||||||
|
# systemName = server.scripttool.getSystemName() + "/" + server.scripttool.getMandant()
|
||||||
|
window = sg.Window("Complete SQL", layout)
|
||||||
|
oldSys = None
|
||||||
|
while True:
|
||||||
|
event, values = window.read()
|
||||||
|
if event == sg.WIN_CLOSED or event == 'Beenden':
|
||||||
|
break
|
||||||
|
elif event == 'clear':
|
||||||
|
window['input'].update("")
|
||||||
|
window['output'].update("")
|
||||||
|
elif event == 'import':
|
||||||
|
try:
|
||||||
|
window['input'].update(window.TKroot.clipboard_get())
|
||||||
|
except:
|
||||||
|
window['input'].update("")
|
||||||
|
window['output'].update("")
|
||||||
|
elif event == 'export':
|
||||||
|
try:
|
||||||
|
window.TKroot.clipboard_clear()
|
||||||
|
window.TKroot.clipboard_append(window['output'].get())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
elif event == 'Vervollständigen':
|
||||||
|
sqlIn = window['input'].get()
|
||||||
|
try:
|
||||||
|
if sqlIn:
|
||||||
|
sys = window['sysenv'].get()
|
||||||
|
if sys != oldSys:
|
||||||
|
oldSys = sys
|
||||||
|
if sys and sys != "-":
|
||||||
|
server = sys.connect()
|
||||||
|
else:
|
||||||
|
server = None
|
||||||
|
if server:
|
||||||
|
sqlOut = server.completeSQL(sqlIn)
|
||||||
|
else:
|
||||||
|
sqlOut = sqlIn
|
||||||
|
sqlOut = prettyPrintSQL(window['formatieren'].get(), sqlOut)
|
||||||
|
else:
|
||||||
|
sqlOut = ""
|
||||||
|
except Exception as e:
|
||||||
|
sqlOut = "ERROR: " + str(e)
|
||||||
|
sg.popup_error_with_traceback("Fehler bei Vervollständigung", e)
|
||||||
|
window['output'].update(value=sqlOut)
|
||||||
|
|
||||||
|
window.close()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
75
examples/importViewUDF.py
Normal file
75
examples/importViewUDF.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||||
|
#
|
||||||
|
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||||
|
#
|
||||||
|
# Use of this source code is governed by an MIT-style
|
||||||
|
# license that can be found in the LICENSE file or at
|
||||||
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import PySimpleGUI as sg # type: ignore
|
||||||
|
import pathlib
|
||||||
|
import PyAPplus64
|
||||||
|
from PyAPplus64 import applus
|
||||||
|
from PyAPplus64 import sql_utils
|
||||||
|
import applus_configs
|
||||||
|
import traceback
|
||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def importViewsUDFs(server, views, udfs, dbanpass):
|
||||||
|
res = ""
|
||||||
|
try:
|
||||||
|
if views or udfs:
|
||||||
|
for env in server.scripttool.getAllEnvironments():
|
||||||
|
res = res + server.importUdfsAndViews(env, views, udfs);
|
||||||
|
res = res + "\n\n";
|
||||||
|
|
||||||
|
for xml in dbanpass:
|
||||||
|
res = res + "Verarbeite " + xml + "\n"
|
||||||
|
xmlRes = server.updateDatabase(xml);
|
||||||
|
if (xmlRes == ""): xmlRes = "OK";
|
||||||
|
res = res + xmlRes
|
||||||
|
res = res + "\n\n"
|
||||||
|
|
||||||
|
sg.popup_scrolled("Importiere", res)
|
||||||
|
except:
|
||||||
|
sg.popup_error("Fehler", traceback.format_exc())
|
||||||
|
|
||||||
|
def importIntoSystem(server, system):
|
||||||
|
try:
|
||||||
|
if (len(sys.argv) < 2):
|
||||||
|
sg.popup_error("Keine Datei zum Import übergeben")
|
||||||
|
return
|
||||||
|
|
||||||
|
views = []
|
||||||
|
udfs = []
|
||||||
|
dbanpass = []
|
||||||
|
errors = ""
|
||||||
|
|
||||||
|
|
||||||
|
for i in range (1, len(sys.argv)):
|
||||||
|
arg = pathlib.Path(sys.argv[i])
|
||||||
|
if arg == server.scripttool.getInstallPathAppServer().joinpath("Database", "View", arg.stem + ".sql"):
|
||||||
|
views.append(arg.stem)
|
||||||
|
elif arg == server.scripttool.getInstallPathAppServer().joinpath("Database", "UDF", arg.stem + ".sql"):
|
||||||
|
udfs.append(arg.stem)
|
||||||
|
elif arg == server.scripttool.getInstallPathAppServer().joinpath("DBChange", arg.stem + ".xml"):
|
||||||
|
dbanpass.append(arg.stem + ".xml")
|
||||||
|
else:
|
||||||
|
errors = errors + " - " + str(arg) + "\n";
|
||||||
|
|
||||||
|
if len(errors) > 0:
|
||||||
|
msg = "Folgende Dateien sind keine View, UDF oder DB-Anpass-Dateien des "+system+"-Systems:\n" + errors;
|
||||||
|
sg.popup_error("Fehler", msg);
|
||||||
|
if views or udfs or dbanpass:
|
||||||
|
importViewsUDFs(server, views, udfs, dbanpass)
|
||||||
|
|
||||||
|
except:
|
||||||
|
sg.popup_error("Fehler", traceback.format_exc())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = PyAPplus64.applusFromConfigFile(applus_configs.serverConfYamlDeploy)
|
||||||
|
importIntoSystem(server, "Deploy");
|
||||||
|
|
||||||
|
|
||||||
|
|
15
examples/importViewUDFDeploy.pyw
Normal file
15
examples/importViewUDFDeploy.pyw
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||||
|
#
|
||||||
|
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||||
|
#
|
||||||
|
# Use of this source code is governed by an MIT-style
|
||||||
|
# license that can be found in the LICENSE file or at
|
||||||
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import importViewUDF
|
||||||
|
import applus_configs
|
||||||
|
import PyAPplus64
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = PyAPplus64.applusFromConfigFile(applus_configs.serverConfYamlDeploy)
|
||||||
|
importViewUDF.importIntoSystem(server, "Deploy")
|
15
examples/importViewUDFTest.pyw
Normal file
15
examples/importViewUDFTest.pyw
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
# Copyright (c) 2023 Thomas Tuerk (kontakt@thomas-tuerk.de)
|
||||||
|
#
|
||||||
|
# This file is part of PyAPplus64 (see https://www.thomas-tuerk.de/de/pyapplus64).
|
||||||
|
#
|
||||||
|
# Use of this source code is governed by an MIT-style
|
||||||
|
# license that can be found in the LICENSE file or at
|
||||||
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
|
import importViewUDF
|
||||||
|
import applus_configs
|
||||||
|
import PyAPplus64
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
server = PyAPplus64.applusFromConfigFile(applus_configs.serverConfYamlTest)
|
||||||
|
importViewUDF.importIntoSystem(server, "Test")
|
@ -48,8 +48,8 @@ def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Op
|
|||||||
print(" InstallPathWebServer:", server.scripttool.getInstallPathWebServer())
|
print(" InstallPathWebServer:", server.scripttool.getInstallPathWebServer())
|
||||||
print(" ServerInfo - Version:", server.scripttool.getServerInfo().find("version").text)
|
print(" ServerInfo - Version:", server.scripttool.getServerInfo().find("version").text)
|
||||||
|
|
||||||
client = server.getWebClient("masterdata/artikel.asmx")
|
client = server.getWebClient("dbenv/dbenv.asmx")
|
||||||
print("ARTIKEL-ASMX Date:", client.service.getServerDate())
|
print("WEB Environment:", client.service.getEnvironment())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main(applus_configs.serverConfYamlTest)
|
main(applus_configs.serverConfYamlTest)
|
||||||
|
@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "PyAPplus64"
|
name = "PyAPplus64"
|
||||||
version = "1.1.1"
|
version = "1.1.2"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
|
||||||
]
|
]
|
||||||
@ -24,7 +24,10 @@ dependencies = [
|
|||||||
'SQLAlchemy',
|
'SQLAlchemy',
|
||||||
'pandas',
|
'pandas',
|
||||||
'XlsxWriter',
|
'XlsxWriter',
|
||||||
'zeep'
|
'zeep',
|
||||||
|
'pysimplegui',
|
||||||
|
'sqlparse',
|
||||||
|
'sqlfmt'
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
@ -14,6 +14,7 @@ from . import applus_scripttool
|
|||||||
from . import applus_usexml
|
from . import applus_usexml
|
||||||
from . import sql_utils
|
from . import sql_utils
|
||||||
import yaml
|
import yaml
|
||||||
|
import json
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
from zeep import Client
|
from zeep import Client
|
||||||
import pyodbc # type: ignore
|
import pyodbc # type: ignore
|
||||||
@ -44,12 +45,8 @@ class APplusServer:
|
|||||||
self.server_settings : applus_server.APplusServerSettings = server_settings
|
self.server_settings : applus_server.APplusServerSettings = server_settings
|
||||||
"""Einstellung für die Verbindung zum APP- und Webserver"""
|
"""Einstellung für die Verbindung zum APP- und Webserver"""
|
||||||
|
|
||||||
self.db_conn = db_settings.connect()
|
self._db_conn_pool = list()
|
||||||
"""
|
"""Eine Liste bestehender DB-Verbindungen"""
|
||||||
Eine pyodbc-Connection zur APplus DB. Diese muss genutzt werden, wenn mehrere Operationen in einer Transaktion
|
|
||||||
genutzt werden sollen. Ansonsten sind die Hilfsmethoden wie :meth:`APplusServer.dbQuery` zu bevorzugen.
|
|
||||||
Diese Connection kann in Verbindung mit den Funktionen aus :mod:`PyAPplus64.applus_db` genutzt werden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.server_conn: applus_server.APplusServerConnection = applus_server.APplusServerConnection(server_settings)
|
self.server_conn: applus_server.APplusServerConnection = applus_server.APplusServerConnection(server_settings)
|
||||||
"""erlaubt den Zugriff auf den AppServer"""
|
"""erlaubt den Zugriff auf den AppServer"""
|
||||||
@ -63,18 +60,61 @@ class APplusServer:
|
|||||||
self.scripttool: applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self)
|
self.scripttool: applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self)
|
||||||
"""erlaubt den einfachen Zugriff auf Funktionen des ScriptTools"""
|
"""erlaubt den einfachen Zugriff auf Funktionen des ScriptTools"""
|
||||||
|
|
||||||
self.client_table = self.server_conn.getAppClient("p2core", "Table")
|
self._client_table = None
|
||||||
self.client_xml = self.server_conn.getAppClient("p2core", "XML")
|
self._client_xml = None
|
||||||
self.client_nummer = self.server_conn.getAppClient("p2system", "Nummer")
|
self._client_nummer = None
|
||||||
self.client_adaptdb = self.server_conn.getAppClient("p2dbtools", "AdaptDB");
|
self._client_adaptdb= None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_table(self) -> Client:
|
||||||
|
if not self._client_table:
|
||||||
|
self._client_table = self.getAppClient("p2core", "Table")
|
||||||
|
return self._client_table
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_xml(self) -> Client:
|
||||||
|
if not self._client_xml:
|
||||||
|
self._client_xml = self.getAppClient("p2core", "XML")
|
||||||
|
return self._client_xml
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_nummer(self) -> Client:
|
||||||
|
if not self._client_nummer:
|
||||||
|
self._client_nummer = self.getAppClient("p2system", "Nummer")
|
||||||
|
return self._client_nummer
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client_adaptdb(self) -> Client:
|
||||||
|
if not self._client_adaptdb:
|
||||||
|
self._client_adaptdb = self.getAppClient("p2dbtools", "AdaptDB")
|
||||||
|
return self._client_adaptdb
|
||||||
|
|
||||||
|
|
||||||
|
def getDBConnection(self) -> pyodbc.Connection:
|
||||||
|
"""
|
||||||
|
Liefert eine pyodbc-Connection zur APplus DB. Diese muss genutzt werden, wenn mehrere Operationen in einer Transaktion
|
||||||
|
genutzt werden sollen. Ansonsten sind die Hilfsmethoden wie :meth:`APplusServer.dbQuery` zu bevorzugen.
|
||||||
|
Diese Connection kann in Verbindung mit den Funktionen aus :mod:`PyAPplus64.applus_db` genutzt werden.
|
||||||
|
Die Verbindung sollte nach Benutzung wieder freigegeben oder geschlossen werden.
|
||||||
|
"""
|
||||||
|
if self._db_conn_pool:
|
||||||
|
return self._db_conn_pool.pop()
|
||||||
|
else:
|
||||||
|
conn = self.db_settings.connect()
|
||||||
|
self._db_conn_pool.append(conn)
|
||||||
|
return conn
|
||||||
|
|
||||||
|
def releaseDBConnection(self, conn : pyodbc.Connection) -> None:
|
||||||
|
"""Gibt eine DB-Connection zur Wiederverwendung frei"""
|
||||||
|
self._db_conn_pool.append(conn)
|
||||||
|
|
||||||
def reconnectDB(self) -> None:
|
def reconnectDB(self) -> None:
|
||||||
try:
|
for conn in self._db_conn_pool:
|
||||||
self.db_conn.close()
|
try:
|
||||||
except:
|
conn.close()
|
||||||
pass
|
except:
|
||||||
self.db_conn = self.db_settings.connect()
|
pass
|
||||||
|
self._db_conn_pool = list()
|
||||||
|
|
||||||
def completeSQL(self, sql: sql_utils.SqlStatement, raw: bool = False) -> str:
|
def completeSQL(self, sql: sql_utils.SqlStatement, raw: bool = False) -> str:
|
||||||
"""
|
"""
|
||||||
@ -97,7 +137,11 @@ class APplusServer:
|
|||||||
"""Führt eine SQL Query aus und liefert alle Zeilen zurück. Das SQL wird zunächst
|
"""Führt eine SQL Query aus und liefert alle Zeilen zurück. Das SQL wird zunächst
|
||||||
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw)
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQueryAll(self.db_conn, sqlC, *args, apply=apply)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.rawQueryAll(conn, sqlC, *args, apply=apply)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def dbQuerySingleValues(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Sequence[Any]:
|
def dbQuerySingleValues(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Sequence[Any]:
|
||||||
"""Führt eine SQL Query aus, die nur eine Spalte zurückliefern soll."""
|
"""Führt eine SQL Query aus, die nur eine Spalte zurückliefern soll."""
|
||||||
@ -107,12 +151,19 @@ class APplusServer:
|
|||||||
"""Führt eine SQL Query aus und führt für jede Zeile die übergeben Funktion aus.
|
"""Führt eine SQL Query aus und führt für jede Zeile die übergeben Funktion aus.
|
||||||
Das SQL wird zunächst vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
Das SQL wird zunächst vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw)
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
applus_db.rawQuery(self.db_conn, sqlC, f, *args)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.rawQuery(conn, sqlC, f, *args)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def dbQuerySingleRow(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Optional[pyodbc.Row]:
|
def dbQuerySingleRow(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Optional[pyodbc.Row]:
|
||||||
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll. Diese Zeile wird geliefert."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw)
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQuerySingleRow(self.db_conn, sqlC, *args)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.rawQuerySingleRow(conn, sqlC, *args)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
def dbQuerySingleRowDict(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Optional[Dict[str, Any]]:
|
def dbQuerySingleRowDict(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Optional[Dict[str, Any]]:
|
||||||
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll.
|
"""Führt eine SQL Query aus, die maximal eine Zeile zurückliefern soll.
|
||||||
@ -127,13 +178,19 @@ class APplusServer:
|
|||||||
"""Führt eine SQL Query aus, die maximal einen Wert zurückliefern soll.
|
"""Führt eine SQL Query aus, die maximal einen Wert zurückliefern soll.
|
||||||
Dieser Wert oder None wird geliefert."""
|
Dieser Wert oder None wird geliefert."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw)
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.rawQuerySingleValue(conn, sqlC, *args)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
def dbExecute(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Any:
|
def dbExecute(self, sql: sql_utils.SqlStatement, *args: Any, raw: bool = False) -> Any:
|
||||||
"""Führt ein SQL Statement (z.B. update oder insert) aus. Das SQL wird zunächst
|
"""Führt ein SQL Statement (z.B. update oder insert) aus. Das SQL wird zunächst
|
||||||
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden."""
|
||||||
sqlC = self.completeSQL(sql, raw=raw)
|
sqlC = self.completeSQL(sql, raw=raw)
|
||||||
return applus_db.rawExecute(self.db_conn, sqlC, *args)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.rawExecute(conn, sqlC, *args)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
def isDBTableKnown(self, table: str) -> bool:
|
def isDBTableKnown(self, table: str) -> bool:
|
||||||
"""Prüft, ob eine Tabelle im System bekannt ist"""
|
"""Prüft, ob eine Tabelle im System bekannt ist"""
|
||||||
@ -164,6 +221,8 @@ class APplusServer:
|
|||||||
Als parameter wird die relative URL der ASMX-Seite erwartet. Die Base-URL automatisch ergänzt.
|
Als parameter wird die relative URL der ASMX-Seite erwartet. Die Base-URL automatisch ergänzt.
|
||||||
Ein Beispiel für eine solche relative URL ist "masterdata/artikel.asmx".
|
Ein Beispiel für eine solche relative URL ist "masterdata/artikel.asmx".
|
||||||
|
|
||||||
|
ACHTUNG: Als Umgebung wird die Umgebung des sich anmeldenden Nutzers verwendet. Sowohl Nutzer als auch Umgebung können sich von den für App-Clients verwendeten Werten unterscheiden. Wenn möglich, sollte ein App-Client verwendet werden.
|
||||||
|
|
||||||
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
|
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
|
||||||
:type package: str
|
:type package: str
|
||||||
:return: den Client
|
:return: den Client
|
||||||
@ -197,7 +256,10 @@ class APplusServer:
|
|||||||
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
Jeder Eintrag enthält eine Liste von Feldern, die zusammen eindeutig sein
|
||||||
müssen.
|
müssen.
|
||||||
"""
|
"""
|
||||||
return applus_db.getUniqueFieldsOfTable(self.db_conn, table)
|
conn = self.getDBConnection()
|
||||||
|
res = applus_db.getUniqueFieldsOfTable(conn, table)
|
||||||
|
self.releaseDBConnection(conn)
|
||||||
|
return res
|
||||||
|
|
||||||
def useXML(self, xml: str) -> Any:
|
def useXML(self, xml: str) -> Any:
|
||||||
"""Ruft ``p2core.xml.usexml`` auf. Wird meist durch ein ``UseXMLRow-Objekt`` aufgerufen."""
|
"""Ruft ``p2core.xml.usexml`` auf. Wird meist durch ein ``UseXMLRow-Objekt`` aufgerufen."""
|
||||||
@ -261,6 +323,33 @@ class APplusServer:
|
|||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
def importUdfsAndViews(self, environment : str, views : [str] = [], udfs : [str] = []) -> str:
|
||||||
|
"""
|
||||||
|
Importiert bestimmte Views und UDFs
|
||||||
|
:param environment: die Umgebung, in die Importiert werden soll
|
||||||
|
:type environment: string
|
||||||
|
:param views: Views, die importiert werden sollen
|
||||||
|
:type views: [string]
|
||||||
|
:param udfs: Views, die importiert werden sollen
|
||||||
|
:type udfs: [string]
|
||||||
|
:return: Infos zur Ausführung
|
||||||
|
:rtype: str
|
||||||
|
"""
|
||||||
|
lbl="";
|
||||||
|
files=[];
|
||||||
|
for v in views:
|
||||||
|
files.append({"type" : 1, "name" : v})
|
||||||
|
for u in udfs:
|
||||||
|
files.append({"type" : 0, "name" : u})
|
||||||
|
|
||||||
|
|
||||||
|
jobId = self.job.createSOAPJob("importing UDFs and Views");
|
||||||
|
self.client_adaptdb.service.importUdfsAndViews(jobId, environment, False, json.dumps(files), "de");
|
||||||
|
res = self.job.getResultURLString(jobId)
|
||||||
|
if res is None: res = "FEHLER";
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
def makeWebLink(self, base: str, **kwargs: Any) -> str:
|
def makeWebLink(self, base: str, **kwargs: Any) -> str:
|
||||||
if not self.server_settings.webserver:
|
if not self.server_settings.webserver:
|
||||||
raise Exception("keine Webserver-BaseURL gesetzt")
|
raise Exception("keine Webserver-BaseURL gesetzt")
|
||||||
@ -286,6 +375,14 @@ class APplusServer:
|
|||||||
def makeWebLinkBauftrag(self, **kwargs: Any) -> str:
|
def makeWebLinkBauftrag(self, **kwargs: Any) -> str:
|
||||||
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs)
|
return self.makeWebLink("wp/bauftragRec.aspx", **kwargs)
|
||||||
|
|
||||||
|
def makeWebLinkAuftrag(self, **kwargs: Any) -> str:
|
||||||
|
return self.makeWebLink("sales/auftragRec.aspx", **kwargs)
|
||||||
|
|
||||||
|
def makeWebLinkVKRahmen(self, **kwargs: Any) -> str:
|
||||||
|
return self.makeWebLink("sales/vkrahmenRec.aspx", **kwargs)
|
||||||
|
|
||||||
|
def makeWebLinkWarenaugang(self, **kwargs: Any) -> str:
|
||||||
|
return self.makeWebLink("sales/warenausgangRec.aspx", **kwargs)
|
||||||
|
|
||||||
def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, env: Optional[str] = None) -> APplusServer:
|
||||||
"""Läd Einstellungen aus einer Config und erzeugt daraus ein APplus-Objekt"""
|
"""Läd Einstellungen aus einer Config und erzeugt daraus ein APplus-Objekt"""
|
||||||
@ -324,3 +421,39 @@ def applusFromConfig(yamlString: str, user: Optional[str] = None, env: Optional[
|
|||||||
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
"""Läd Einstellungen aus einer Config-Datei und erzeugt daraus ein APplus-Objekt"""
|
||||||
yamlDict = yaml.safe_load(yamlString)
|
yamlDict = yaml.safe_load(yamlString)
|
||||||
return applusFromConfigDict(yamlDict, user=user, env=env)
|
return applusFromConfigDict(yamlDict, user=user, env=env)
|
||||||
|
|
||||||
|
|
||||||
|
class APplusServerConfigDescription:
|
||||||
|
"""
|
||||||
|
Beschreibung einer Configuration bestehend aus Config-Datei, Nutzer und Umgebung.
|
||||||
|
|
||||||
|
:param descr: Beschreibung als String, nur für Ausgabe gedacht
|
||||||
|
:type descr: str
|
||||||
|
|
||||||
|
:param yamlfile: die Datei
|
||||||
|
:type yamlfile: 'FileDescriptorOrPath'
|
||||||
|
|
||||||
|
:param user: der Nutzer
|
||||||
|
:type user: Optional[str]
|
||||||
|
|
||||||
|
:param env: die Umgebung
|
||||||
|
:type env: Optional[str]
|
||||||
|
"""
|
||||||
|
def __init__(self,
|
||||||
|
descr: str,
|
||||||
|
yamlfile: 'FileDescriptorOrPath',
|
||||||
|
user:Optional[str] = None,
|
||||||
|
env:Optional[str] = None):
|
||||||
|
self.descr = descr
|
||||||
|
self.yamlfile = yamlfile
|
||||||
|
self.user = user
|
||||||
|
self.env = env
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.descr
|
||||||
|
|
||||||
|
def connect(self) -> APplusServer:
|
||||||
|
return applusFromConfigFile(self.yamlfile, user=self.user, env=self.env)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
from zeep import Client
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -23,7 +24,14 @@ class APplusJob:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server: 'APplusServer') -> None:
|
def __init__(self, server: 'APplusServer') -> None:
|
||||||
self.client = server.getAppClient("p2core", "Job")
|
self.server = server
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> Client:
|
||||||
|
if not self._client:
|
||||||
|
self._client = self.server.getAppClient("p2core", "Job")
|
||||||
|
return self._client
|
||||||
|
|
||||||
def createSOAPJob(self, bez: str) -> str:
|
def createSOAPJob(self, bez: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
@ -10,6 +10,7 @@ from .applus import APplusServer
|
|||||||
from . import sql_utils
|
from . import sql_utils
|
||||||
import lxml.etree as ET # type: ignore
|
import lxml.etree as ET # type: ignore
|
||||||
from typing import Optional, Tuple, Set
|
from typing import Optional, Tuple, Set
|
||||||
|
from zeep import Client
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
|
|
||||||
@ -57,7 +58,14 @@ class APplusScriptTool:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server: APplusServer) -> None:
|
def __init__(self, server: APplusServer) -> None:
|
||||||
self.client = server.getAppClient("p2script", "ScriptTool")
|
self.server = server
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> Client:
|
||||||
|
if not self._client:
|
||||||
|
self._client = self.server.getAppClient("p2script", "ScriptTool")
|
||||||
|
return self._client
|
||||||
|
|
||||||
def getCurrentDate(self) -> str:
|
def getCurrentDate(self) -> str:
|
||||||
return self.client.service.getCurrentDate()
|
return self.client.service.getCurrentDate()
|
||||||
@ -181,3 +189,18 @@ class APplusScriptTool:
|
|||||||
:rtype: ET.Element
|
:rtype: ET.Element
|
||||||
"""
|
"""
|
||||||
return ET.fromstring(self.getServerInfoString())
|
return ET.fromstring(self.getServerInfoString())
|
||||||
|
|
||||||
|
def getAllEnvironments(self) -> [str]:
|
||||||
|
"""
|
||||||
|
Liefert alle Umgebungen
|
||||||
|
|
||||||
|
:return: die gefundenen Umgebungen
|
||||||
|
:rtype: [str]
|
||||||
|
"""
|
||||||
|
|
||||||
|
envs = []
|
||||||
|
envString = self.client.service.getAllEnvironmentsInMasterDatabase()
|
||||||
|
for e in envString.split(","):
|
||||||
|
envs.append(e.split(":")[0])
|
||||||
|
return envs
|
||||||
|
|
||||||
|
@ -101,6 +101,8 @@ class APplusServerConnection:
|
|||||||
Als parameter wird die relative URL der ASMX-Seite erwartet. Die Base-URL automatisch ergänzt.
|
Als parameter wird die relative URL der ASMX-Seite erwartet. Die Base-URL automatisch ergänzt.
|
||||||
Ein Beispiel für eine solche relative URL ist "masterdata/artikel.asmx".
|
Ein Beispiel für eine solche relative URL ist "masterdata/artikel.asmx".
|
||||||
|
|
||||||
|
ACHTUNG: Als Umgebung wird die Umgebung des sich anmeldenden Nutzers verwendet. Sowohl Nutzer als auch Umgebung können sich von den für App-Clients verwendeten Werten unterscheiden. Wenn möglich, sollte ein App-Client verwendet werden.
|
||||||
|
|
||||||
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
|
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
|
||||||
:type package: str
|
:type package: str
|
||||||
:return: den Client
|
:return: den Client
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
# https://opensource.org/licenses/MIT.
|
# https://opensource.org/licenses/MIT.
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Optional, Dict, Any, Callable, Sequence
|
from typing import TYPE_CHECKING, Optional, Dict, Any, Callable, Sequence
|
||||||
|
from zeep import Client
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .applus import APplusServer
|
from .applus import APplusServer
|
||||||
@ -22,8 +23,16 @@ class APplusSysConf:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, server: 'APplusServer') -> None:
|
def __init__(self, server: 'APplusServer') -> None:
|
||||||
self.client = server.getAppClient("p2system", "SysConf")
|
|
||||||
self.cache: Dict[str, type] = {}
|
self.cache: Dict[str, type] = {}
|
||||||
|
self.server = server
|
||||||
|
self._client = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def client(self) -> Client:
|
||||||
|
if not self._client:
|
||||||
|
self._client = self.server.getAppClient("p2system", "SysConf")
|
||||||
|
return self._client
|
||||||
|
|
||||||
|
|
||||||
def clearCache(self) -> None:
|
def clearCache(self) -> None:
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
@ -21,6 +21,7 @@ APplus. Oft ist es sinnvoll, solche Parameter zu verwenden.
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import datetime
|
import datetime
|
||||||
|
import numpy
|
||||||
from typing import Set, Sequence, Union, Optional, cast, List
|
from typing import Set, Sequence, Union, Optional, cast, List
|
||||||
|
|
||||||
|
|
||||||
@ -158,7 +159,7 @@ def formatSqlValue(v: SqlValue) -> str:
|
|||||||
if v is None:
|
if v is None:
|
||||||
raise Exception("formatSqlValue: null not supported")
|
raise Exception("formatSqlValue: null not supported")
|
||||||
|
|
||||||
if isinstance(v, (int, float, SqlField)):
|
if isinstance(v, (int, float, SqlField, numpy.int64)):
|
||||||
return str(v)
|
return str(v)
|
||||||
elif isinstance(v, str):
|
elif isinstance(v, str):
|
||||||
return formatSqlValueString(v)
|
return formatSqlValueString(v)
|
||||||
|
Loading…
Reference in New Issue
Block a user