diff --git a/Changelog.md b/Changelog.md index 334c9b2..571769a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ # Changelog +## 23.08.2023 v1.1.1 +- `APplusJob` für einfachen Zugriff auf `p2core.Job` implementiert +- Funktion für Ausführung von DB-Anpass Dateien implementiert + ## 27.07.2023 v1.1.0 - implementiere Zugriff auf ASMX Seiten - `getClient` -> `getAppClient` umbenannt diff --git a/docs/source/conf.py b/docs/source/conf.py index 0154e20..0fe40b9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -14,8 +14,8 @@ sys.path.append('../src/') project = 'PyAPplus64' copyright = '2023, Thomas Tuerk' author = 'Thomas Tuerk' -version = '1.1.0' -release = '1.1.0' +version = '1.1.1' +release = '1.1.1' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/source/generated/PyAPplus64.rst b/docs/source/generated/PyAPplus64.rst index b28fbb0..9007190 100644 --- a/docs/source/generated/PyAPplus64.rst +++ b/docs/source/generated/PyAPplus64.rst @@ -20,6 +20,14 @@ PyAPplus64.applus\_db module :undoc-members: :show-inheritance: +PyAPplus64.applus\_job module +----------------------------- + +.. automodule:: PyAPplus64.applus_job + :members: + :undoc-members: + :show-inheritance: + PyAPplus64.applus\_scripttool module ------------------------------------ diff --git a/pyproject.toml b/pyproject.toml index b157409..b1ded0c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "PyAPplus64" -version = "1.1.0" +version = "1.1.1" authors = [ { name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" }, ] diff --git a/src/PyAPplus64/applus.py b/src/PyAPplus64/applus.py index a07bf4f..8a33a24 100644 --- a/src/PyAPplus64/applus.py +++ b/src/PyAPplus64/applus.py @@ -7,6 +7,7 @@ # https://opensource.org/licenses/MIT. from . import applus_db +from . import applus_job from . import applus_server from . import applus_sysconf from . import applus_scripttool @@ -56,12 +57,17 @@ class APplusServer: self.sysconf: applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self) """erlaubt den Zugriff auf die Sysconfig""" + self.job: applus_job.APplusJob = applus_job.APplusJob(self) + """erlaubt Arbeiten mit Jobs""" + self.scripttool: applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self) """erlaubt den einfachen Zugriff auf Funktionen des ScriptTools""" self.client_table = self.server_conn.getAppClient("p2core", "Table") self.client_xml = self.server_conn.getAppClient("p2core", "XML") self.client_nummer = self.server_conn.getAppClient("p2system", "Nummer") + self.client_adaptdb = self.server_conn.getAppClient("p2dbtools", "AdaptDB"); + def reconnectDB(self) -> None: try: @@ -123,6 +129,12 @@ class APplusServer: sqlC = self.completeSQL(sql, raw=raw) return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args) + 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 + vom Server angepasst, so dass z.B. Mandanteninformation hinzugefügt werden.""" + sqlC = self.completeSQL(sql, raw=raw) + return applus_db.rawExecute(self.db_conn, sqlC, *args) + def isDBTableKnown(self, table: str) -> bool: """Prüft, ob eine Tabelle im System bekannt ist""" sql = "select count(*) from SYS.TABLES T where T.NAME=?" @@ -231,6 +243,24 @@ class APplusServer: """ return self.client_nummer.service.nextNumber(obj) + def updateDatabase(self, file : str) -> str: + """ + Führt eine DBAnpass-xml Datei aus. + :param file: DB-Anpass Datei, die ausgeführt werden soll + :type file: string + :return: Infos zur Ausführung + :rtype: str + """ + jobId = self.job.createSOAPJob("run DBAnpass " + file); + self.client_adaptdb.service.updateDatabase(jobId, "de", file); + res = self.job.getResultURLString(jobId) + if res is None: res = "FEHLER"; + if (res == "Folgende Befehle konnten nicht ausgeführt werden:\n\n"): + return "" + else: + return res + + def makeWebLink(self, base: str, **kwargs: Any) -> str: if not self.server_settings.webserver: raise Exception("keine Webserver-BaseURL gesetzt") diff --git a/src/PyAPplus64/applus_db.py b/src/PyAPplus64/applus_db.py index 9a1b0be..3ae1c43 100644 --- a/src/PyAPplus64/applus_db.py +++ b/src/PyAPplus64/applus_db.py @@ -108,6 +108,11 @@ def rawQuerySingleValue(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) else: return None +def rawExecute(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any) -> Any: + """Führt ein SQL Statement direkt aus""" + _logSQLWithArgs(sql, *args) + with cnxn.cursor() as cursor: + return cursor.execute(str(sql), *args) def getUniqueFieldsOfTable(cnxn: pyodbc.Connection, table: str) -> Dict[str, List[str]]: """ diff --git a/src/PyAPplus64/applus_job.py b/src/PyAPplus64/applus_job.py new file mode 100644 index 0000000..2911f3a --- /dev/null +++ b/src/PyAPplus64/applus_job.py @@ -0,0 +1,195 @@ +# 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. + +from typing import TYPE_CHECKING, Optional +import uuid + +if TYPE_CHECKING: + from .applus import APplusServer + + +class APplusJob: + """ + Zugriff auf Jobs + + :param server: die Verbindung zum Server + :type server: APplusServer + + """ + + def __init__(self, server: 'APplusServer') -> None: + self.client = server.getAppClient("p2core", "Job") + + def createSOAPJob(self, bez: str) -> str: + """ + Erzeugt einen neuen SOAP Job mit der gegebenen Bezeichnung und liefert die neue JobID. + :param bez: die Bezeichnung des neuen Jobs + :type bez: str + :return: die neue JobID + :rtype: str + """ + jobId = str(uuid.uuid4()) + self.client.service.create(jobId, "SOAP", "0", "about:soapcall", bez) + return jobId + + def restart(self, jobId: str) -> str: + """ + Startet einen Job neu + :param jobId: die ID des Jobs + :type jobId: str + :return: die URL des Jobs + :rtype: str + """ + return self.client.service.restart(jobId) + + def setResultURL(self, jobId: str, resurl: str) -> None: + """ + Setzt die ResultURL eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :param resurl: die neue Result-URL + :type resurl: str + """ + self.client.service.setResultURL(jobId, resurl) + + def getResultURL(self, jobId: str) -> str: + """ + Liefert die ResultURL eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :return: die Result-URL + :rtype: str + """ + return self.client.service.getResultURL(jobId) + + def getResultURLString(self, jobId: str) -> Optional[str]: + """ + Liefert die ResultURL eines Jobs, wobei ein evtl. Präfix "retstring://" entfernt wird und + alle anderen Werte durch None ersetzt werden. + :param jobId: die ID des Jobs + :type jobId: str + :return: die Result-URL als String + :rtype: str + """ + res = self.getResultURL(jobId) + if res is None: + return None + + if res.startswith("retstring://"): + return res[12:] + return None + + def setPtURL(self, jobId: str, pturl: str) -> None: + """ + Setzt die ResultURL eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :param pturl: die neue PtURL + :type pturl: str + """ + self.client.service.setPtURL(jobId, pturl) + + def getPtURL(self, jobId: str) -> str: + """ + Liefert die PtURL eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :return: die Pt-URL + :rtype: str + """ + return self.client.service.getPtURL(jobId) + + def setResult(self, jobId: str, res: str) -> None: + """ + Setzt das Result eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :param res: das neue Result + :type res: str + """ + self.client.service.setResult(jobId, res) + + def getResult(self, jobId: str) -> str: + """ + Liefert das Result eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :return: das Result + :rtype: str + """ + return self.client.service.getResult(jobId) + + def setInfo(self, jobId: str, info: str) -> bool: + """ + Setzt die Informationen zu dem Job + :param jobId: die ID des Jobs + :type jobId: str + :param info: die neuen Infos + :type info: str + """ + return self.client.service.setInfo(jobId, info) + + def getInfo(self, jobId: str) -> str: + """ + Liefert die Info eines Jobs + :param jobId: die ID des Jobs + :type jobId: str + :return: die Info + :rtype: str + """ + return self.client.service.getInfo(jobId) + + def getStatus(self, jobId: str) -> str: + """ + Liefert Informationen zum Job + :param jobId: die ID des Jobs + :type jobId: str + :return: die Infos + :rtype: str + """ + return self.client.service.getStatus(jobId) + + def setPosition(self, jobId: str, pos: int, max: int) -> bool: + """ + Schrittfunktion + :param jobId: die ID des Jobs + :type jobId: str + :param pos: Position + :type pos: int + :param max: Anzahl Schritte in Anzeige + :type max: int + """ + return self.client.service.setPosition(jobId, pos, max) + + def start(self, jobId: str) -> bool: + """ + Startet einen Job + :param jobId: die ID des Jobs + :type jobId: str + """ + return self.client.service.start(jobId) + + def kill(self, jobId: str) -> None: + """ + Startet einen Job + :param jobId: die ID des Jobs + :type jobId: str + """ + self.client.service.start(jobId) + + def finish(self, jobId: str, status: int, resurl: str) -> bool: + """ + Beendet den Job + :param jobId: die ID des Jobs + :type jobId: str + :param status: der Status 2 (OK), 3 (Fehler) + :type status: int + :param resurl: die neue resurl des Jobs + :type resurl: str + """ + return self.client.service.finish(jobId, status, resurl) diff --git a/src/PyAPplus64/sql_utils.py b/src/PyAPplus64/sql_utils.py index 0dbf4fe..8a50e73 100644 --- a/src/PyAPplus64/sql_utils.py +++ b/src/PyAPplus64/sql_utils.py @@ -392,7 +392,7 @@ class SqlConditionBinComp(SqlConditionPrepared): :type value2: SqlValue """ def __init__(self, op: str, value1: SqlValue, value2: SqlValue): - if not value1 or not value2: + if value1 is None or value2 is None: raise Exception("SqlConditionBinComp: value not provided") cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2))