2 Commits
1.0.1 ... 1.1.1

Author SHA1 Message Date
e7fe3fb037 APplusJob implementiert 2023-08-23 17:29:38 +02:00
599339a270 getWebClient implementiert 2023-07-27 11:56:20 +02:00
16 changed files with 391 additions and 76 deletions

View File

@ -1,5 +1,15 @@
# Changelog # 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
- `getWebClient` implementiert
- dies benötigt Paket `requests-negotiate-sspi`, das leider nur für Windows verfügbar ist
## 06.05.2023 v1.0.1 ## 06.05.2023 v1.0.1
- Code-Cleanup mit Hilfe von flake8 - Code-Cleanup mit Hilfe von flake8
- Bugfix: neue Python 3.10 Syntax entfernt - Bugfix: neue Python 3.10 Syntax entfernt

View File

@ -1,21 +1,21 @@
# PyAPplus64 # PyAPplus64
## Beschreibung ## Beschreibung
Das Paket `PyAPplus64` enthält eine Sammlung von Python Tools für die Interaktion mit dem ERP-System APplus 6.4. Das Paket `PyAPplus64` enthält eine Sammlung von Python Tools für die Interaktion mit dem ERP-System APplus 6.4.
Es sollte auch für andere APplus Versionen nützlich sein. Es sollte auch für andere APplus Versionen nützlich sein.
Zielgruppe sind APplus-Administratoren und Anpassungs-Entwickler. Die Tools erlauben u.a. Zielgruppe sind APplus-Administratoren und Anpassungs-Entwickler. Die Tools erlauben u.a.
- einfacher Zugriff auf SOAP-Schnittstelle des App-Servers - einfacher Zugriff auf SOAP-Schnittstelle des App-Servers
+ damit Zugriff auf SysConfig + damit Zugriff auf SysConfig
+ Zugriff auf Tools `nextNumber` für Erzeugung der nächsten Nummer für ein Business-Object + Zugriff auf Tools `nextNumber` für Erzeugung der nächsten Nummer für ein Business-Object
+ ... + ...
- Zugriff auf APplus DB per direktem DB-Zugriff und mittels SOAP - Zugriff auf APplus DB per direktem DB-Zugriff und mittels SOAP
+ automatischer Aufruf von `completeSQL`, um per App-Server SQL-Statements um z.B. Mandanten erweitern zu lassen + automatischer Aufruf von `completeSQL`, um per App-Server SQL-Statements um z.B. Mandanten erweitern zu lassen
+ Tools für einfache Benutzung von `useXML`, d.h. für das Einfügen, Löschen und Ändern von Datensätzen + Tools für einfache Benutzung von `useXML`, d.h. für das Einfügen, Löschen und Ändern von Datensätzen
mit Hilfe des APP-Servers. Genau wie bei Änderungen an Datensätzen über die Web-Oberfläche und im Gegensatz mit Hilfe des APP-Servers. Genau wie bei Änderungen an Datensätzen über die Web-Oberfläche und im Gegensatz
zum direkten Zugriff über die Datenbank werden dabei evtl. zusätzliche zum direkten Zugriff über die Datenbank werden dabei evtl. zusätzliche
Checks ausgeführt, bestimmte Felder automatisch gesetzt oder bestimmte Aktionen angestoßen. Checks ausgeführt, bestimmte Felder automatisch gesetzt oder bestimmte Aktionen angestoßen.
- das Duplizieren von Datensätzen - das Duplizieren von Datensätzen
+ zu kopierende Felder aus XML-Definitionen werden ausgewertet + zu kopierende Felder aus XML-Definitionen werden ausgewertet
+ Abhängige Objekte können einfach ebenfalls mit-kopiert werden + Abhängige Objekte können einfach ebenfalls mit-kopiert werden
@ -35,9 +35,9 @@ aus, dass im Laufe der Zeit weitere Features hinzukommen.
## Warnung ## Warnung
`PyAPplus64` erlaubt den schreibenden Zugriff auf die APplus Datenbank und beliebige `PyAPplus64` erlaubt den schreibenden Zugriff auf die APplus Datenbank und beliebige
Aufrufe von SOAP-Methoden. Unsachgemäße Nutzung kann Ihre Daten zerstören. Benutzen Sie Aufrufe von SOAP-Methoden. Unsachgemäße Nutzung kann Ihre Daten zerstören. Benutzen Sie
`PyAPplus64` daher bitte vorsichtig. `PyAPplus64` daher bitte vorsichtig.
## Installation ## Installation
@ -47,6 +47,14 @@ PyAPplus64 wurde auf PyPi veröffentlicht. Es lässt sich daher einfach mittel `
pip install PyAPplus64 pip install PyAPplus64
```` ````
Zur Nutzung von ASMX-Seiten ist die Authentifizierungsmethode Negotiate nötig. Für diese muss `requests-negotiate-sspi` installiert werden,
was aber leider nur unter Windows verfügbar ist.
````
pip install requests-negotiate-sspi
````
## Links ## Links
- [PyPi](https://pypi.org/project/PyAPplus64/) - [PyPi](https://pypi.org/project/PyAPplus64/)

View File

@ -3,8 +3,8 @@ Abhängigkeiten
pyodbc pyodbc
------ ------
Für die Datenbankverbindung wird ``pyodbc`` (``python -m pip install pyodbc``) verwendet. Für die Datenbankverbindung wird ``pyodbc`` (``python -m pip install pyodbc``) verwendet.
Der passende ODBC Treiber, MS SQL Server 2012 Native Client, wird zusätzlich benötigt. Der passende ODBC Treiber, MS SQL Server 2012 Native Client, wird zusätzlich benötigt.
Dieser kann von Microsoft bezogen werden. Dieser kann von Microsoft bezogen werden.
@ -13,18 +13,25 @@ zeep
Die Soap-Library ``zeep`` wird benutzt (``python -m pip install zeep``). Die Soap-Library ``zeep`` wird benutzt (``python -m pip install zeep``).
requests-negotiate-sspi
-----------------------
Die Authentifzierungsmethode Negotiate Wird für Zugriffe auf ASMX-Seiten benutzt (``python -m pip install requests-negotiate-sspi``).
Leider ist dies nur unter Windows verfügbar. Alle anderen Funktionen können aber auch ohne
dieses Paket benutzt werden.
PyYaml PyYaml
------ ------
Die Library ``pyyaml`` wird für Config-Dateien benutzt (``python -m pip install pyyaml``). Die Library ``pyyaml`` wird für Config-Dateien benutzt (``python -m pip install pyyaml``).
Sphinx Sphinx
------ ------
Diese Dokumentation ist mit Sphinx geschrieben. Diese Dokumentation ist mit Sphinx geschrieben.
``python -m pip install sphinx``. Dokumentation ist im Unterverzeichnis ``python -m pip install sphinx``. Dokumentation ist im Unterverzeichnis
`docs` zu finden. Sie kann mittels ``make.bat html`` erzeugt werden, `docs` zu finden. Sie kann mittels ``make.bat html`` erzeugt werden,
dies ruft intern ``sphinx-build -M html source build`` auf. Die Dokumentation dies ruft intern ``sphinx-build -M html source build`` auf. Die Dokumentation
der Python-API sollte evtl. vorher der Python-API sollte evtl. vorher
mittels ``sphinx-apidoc -T -f ../src/PyAPplus64 -o source/generated`` erzeugt mittels ``sphinx-apidoc -T -f ../src/PyAPplus64 -o source/generated`` erzeugt
oder aktualisiert werden. Evtl. können 2 Aufrufe von ``make.bat html`` sinnvoll oder aktualisiert werden. Evtl. können 2 Aufrufe von ``make.bat html`` sinnvoll
@ -36,5 +43,6 @@ Die erzeugte Doku findet sich im Verzeichnis ``build/html``.
Pandas / SqlAlchemy / xlsxwriter 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`).

View File

@ -85,10 +85,10 @@ Zugriff auf die Sysconf möglich::
print (server.sysconf.getList("STAMM", "EULAENDER")) print (server.sysconf.getList("STAMM", "EULAENDER"))
Dank der Bibliothek `zeep` ist es auch sehr einfach möglich, auf beliebige SOAP-Methoden zuzugreifen. Dank der Bibliothek `zeep` ist es auch sehr einfach möglich, auf beliebige SOAP-Methoden zuzugreifen.
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
zugegriffen werden:: des APP-Servers zugegriffen werden::
client = server.server_conn.getClient("p2system", "SysConf"); client = server.server_conn.getAppClient("p2system", "SysConf");
print (client.service.getString("STAMM", "MYLAND")) print (client.service.getString("STAMM", "MYLAND"))

View File

@ -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.0.1' version = '1.1.1'
release = '1.0.1' release = '1.1.1'
# -- 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

View File

@ -20,6 +20,14 @@ PyAPplus64.applus\_db module
:undoc-members: :undoc-members:
:show-inheritance: :show-inheritance:
PyAPplus64.applus\_job module
-----------------------------
.. automodule:: PyAPplus64.applus_job
:members:
:undoc-members:
:show-inheritance:
PyAPplus64.applus\_scripttool module PyAPplus64.applus\_scripttool module
------------------------------------ ------------------------------------

View File

@ -16,7 +16,10 @@ appserver : {
env : "default-umgebung" # hier wirklich Umgebung, nicht Mandant verwenden env : "default-umgebung" # hier wirklich Umgebung, nicht Mandant verwenden
} }
webserver : { webserver : {
baseurl : "http://some-server/APplusProd6/" baseurl : "http://some-server/APplusProd6/",
user : null, # oft "ASOL.Projects", wenn nicht gesetzt, wird aktueller Windows-Nutzer verwendet
userDomain : null, # Domain für ASOL.PROJECTS
password : null # das Passwort
} }
dbserver : { dbserver : {
server : "some-server", server : "some-server",

View File

@ -48,6 +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")
print("ARTIKEL-ASMX Date:", client.service.getServerDate())
if __name__ == "__main__": if __name__ == "__main__":
main(applus_configs.serverConfYamlTest) main(applus_configs.serverConfYamlTest)

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "PyAPplus64" name = "PyAPplus64"
version = "1.0.1" version = "1.1.1"
authors = [ authors = [
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" }, { name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
] ]

View File

@ -7,6 +7,7 @@
# https://opensource.org/licenses/MIT. # https://opensource.org/licenses/MIT.
from . import applus_db from . import applus_db
from . import applus_job
from . import applus_server from . import applus_server
from . import applus_sysconf from . import applus_sysconf
from . import applus_scripttool from . import applus_scripttool
@ -35,14 +36,13 @@ class APplusServer:
""" """
def __init__(self, def __init__(self,
db_settings: applus_db.APplusDBSettings, db_settings: applus_db.APplusDBSettings,
server_settings: applus_server.APplusAppServerSettings, server_settings: applus_server.APplusServerSettings):
web_settings: applus_server.APplusWebServerSettings):
self.db_settings: applus_db.APplusDBSettings = db_settings self.db_settings: applus_db.APplusDBSettings = db_settings
"""Die Einstellungen für die Datenbankverbindung""" """Die Einstellungen für die Datenbankverbindung"""
self.web_settings: applus_server.APplusWebServerSettings = web_settings self.server_settings : applus_server.APplusServerSettings = server_settings
"""Die Einstellungen für die Datenbankverbindung""" """Einstellung für die Verbindung zum APP- und Webserver"""
self.db_conn = db_settings.connect() self.db_conn = db_settings.connect()
""" """
@ -57,12 +57,17 @@ class APplusServer:
self.sysconf: applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self) self.sysconf: applus_sysconf.APplusSysConf = applus_sysconf.APplusSysConf(self)
"""erlaubt den Zugriff auf die Sysconfig""" """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) 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.getClient("p2core", "Table") self.client_table = self.server_conn.getAppClient("p2core", "Table")
self.client_xml = self.server_conn.getClient("p2core", "XML") self.client_xml = self.server_conn.getAppClient("p2core", "XML")
self.client_nummer = self.server_conn.getClient("p2system", "Nummer") self.client_nummer = self.server_conn.getAppClient("p2system", "Nummer")
self.client_adaptdb = self.server_conn.getAppClient("p2dbtools", "AdaptDB");
def reconnectDB(self) -> None: def reconnectDB(self) -> None:
try: try:
@ -124,15 +129,21 @@ class APplusServer:
sqlC = self.completeSQL(sql, raw=raw) sqlC = self.completeSQL(sql, raw=raw)
return applus_db.rawQuerySingleValue(self.db_conn, sqlC, *args) 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: def isDBTableKnown(self, table: str) -> bool:
"""Prüft, ob eine Tabelle im System bekannt ist""" """Prüft, ob eine Tabelle im System bekannt ist"""
sql = "select count(*) from SYS.TABLES T where T.NAME=?" sql = "select count(*) from SYS.TABLES T where T.NAME=?"
c = self.dbQuerySingleValue(sql, table) c = self.dbQuerySingleValue(sql, table)
return (c > 0) return (c > 0)
def getClient(self, package: str, name: str) -> Client: def getAppClient(self, package: str, name: str) -> Client:
"""Erzeugt einen zeep - Client. """Erzeugt einen zeep - Client für den APP-Server.
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden. Mittels dieses Clients kann eines WSDL Schnittstelle des APP-Servers angesprochen werden.
Wird als *package* "p2core" und als *name* "Table" verwendet und der Wird als *package* "p2core" und als *name* "Table" verwendet und der
resultierende client "client" genannt, dann kann resultierende client "client" genannt, dann kann
z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen
@ -145,7 +156,20 @@ class APplusServer:
:return: den Client :return: den Client
:rtype: Client :rtype: Client
""" """
return self.server_conn.getClient(package, name) return self.server_conn.getAppClient(package, name)
def getWebClient(self, url: str) -> Client:
"""Erzeugt einen zeep - Client für den Web-Server.
Mittels dieses Clients kann die von einer ASMX-Seite zur Verfügung gestellte Schnittstelle angesprochen werden.
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".
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
:type package: str
:return: den Client
:rtype: Client
"""
return self.server_conn.getWebClient(url)
def getTableFields(self, table: str, isComputed: Optional[bool] = None) -> Set[str]: def getTableFields(self, table: str, isComputed: Optional[bool] = None) -> Set[str]:
""" """
@ -219,11 +243,29 @@ class APplusServer:
""" """
return self.client_nummer.service.nextNumber(obj) 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: def makeWebLink(self, base: str, **kwargs: Any) -> str:
if not self.web_settings.baseurl: if not self.server_settings.webserver:
raise Exception("keine Webserver-BaseURL gesetzt") raise Exception("keine Webserver-BaseURL gesetzt")
url = str(self.web_settings.baseurl) + base url = str(self.server_settings.webserver) + base
firstArg = True firstArg = True
for arg, argv in kwargs.items(): for arg, argv in kwargs.items():
if not (argv is None): if not (argv is None):
@ -251,20 +293,21 @@ def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, e
user = yamlDict["appserver"]["user"] user = yamlDict["appserver"]["user"]
if env is None or env == '': if env is None or env == '':
env = yamlDict["appserver"]["env"] env = yamlDict["appserver"]["env"]
app_server = applus_server.APplusAppServerSettings( server_settings = applus_server.APplusServerSettings(
webserver=yamlDict.get("webserver", {}).get("baseurl", None),
appserver=yamlDict["appserver"]["server"], appserver=yamlDict["appserver"]["server"],
appserverPort=yamlDict["appserver"]["port"], appserverPort=yamlDict["appserver"]["port"],
user=user, # type: ignore user=user, # type: ignore
env=env) env=env,
web_server = applus_server.APplusWebServerSettings( webserverUser=yamlDict.get("webserver", {}).get("user", None),
baseurl=yamlDict.get("webserver", {}).get("baseurl", None) webserverUserDomain=yamlDict.get("webserver", {}).get("userDomain", None),
) webserverPassword=yamlDict.get("webserver", {}).get("password", None))
dbparams = applus_db.APplusDBSettings( dbparams = applus_db.APplusDBSettings(
server=yamlDict["dbserver"]["server"], server=yamlDict["dbserver"]["server"],
database=yamlDict["dbserver"]["db"], database=yamlDict["dbserver"]["db"],
user=yamlDict["dbserver"]["user"], user=yamlDict["dbserver"]["user"],
password=yamlDict["dbserver"]["password"]) password=yamlDict["dbserver"]["password"])
return APplusServer(dbparams, app_server, web_server) return APplusServer(dbparams, server_settings)
def applusFromConfigFile(yamlfile: 'FileDescriptorOrPath', def applusFromConfigFile(yamlfile: 'FileDescriptorOrPath',

View File

@ -108,6 +108,11 @@ def rawQuerySingleValue(cnxn: pyodbc.Connection, sql: SqlStatement, *args: Any)
else: else:
return None 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]]: def getUniqueFieldsOfTable(cnxn: pyodbc.Connection, table: str) -> Dict[str, List[str]]:
""" """

View File

@ -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)

View File

@ -57,7 +57,7 @@ class APplusScriptTool:
""" """
def __init__(self, server: APplusServer) -> None: def __init__(self, server: APplusServer) -> None:
self.client = server.getClient("p2script", "ScriptTool") self.client = server.getAppClient("p2script", "ScriptTool")
def getCurrentDate(self) -> str: def getCurrentDate(self) -> str:
return self.client.service.getCurrentDate() return self.client.service.getCurrentDate()

View File

@ -13,56 +13,66 @@ from zeep.transports import Transport
from zeep.cache import SqliteCache from zeep.cache import SqliteCache
from typing import Optional, Dict from typing import Optional, Dict
try:
from requests_negotiate_sspi import HttpNegotiateAuth
auth_negotiate_present = True
except:
auth_negotiate_present = False
class APplusAppServerSettings: class APplusServerSettings:
""" """
Einstellungen, mit welchem APplus App-Server sich verbunden werden soll. Einstellungen, mit welchem APplus App- and Web-Server sich verbunden werden soll.
""" """
def __init__(self, appserver: str, appserverPort: int, user: str, env: Optional[str] = None): def __init__(self, webserver: str, appserver: str, appserverPort: int, user: str, env: Optional[str] = None, webserverUser : Optional[str] = None, webserverUserDomain : Optional[str] = None, webserverPassword : Optional[str] = None):
self.appserver = appserver self.appserver = appserver
self.appserverPort = appserverPort self.appserverPort = appserverPort
self.user = user self.user = user
self.env = env self.env = env
self.webserver = webserver
class APplusWebServerSettings: self.webserverUser = webserverUser
""" self.webserverUserDomain = webserverUserDomain
Einstellungen, mit welchem APplus Web-Server sich verbunden werden soll. self.webserverPassword = webserverPassword
"""
def __init__(self, baseurl: Optional[str] = None):
self.baseurl: Optional[str] = baseurl
try: try:
assert (isinstance(self.baseurl, str)) if not (self.webserver[-1] == "/"):
if not (self.baseurl is None) and not (self.baseurl[-1] == "/"): self.webserver = self.webserver + "/"
self.baseurl = self.baseurl + "/"
except: except:
pass pass
class APplusServerConnection: class APplusServerConnection:
"""Verbindung zu einem APplus APP-Server """Verbindung zu einem APplus APP- und Web-Server
:param settings: die Einstellungen für die Verbindung mit dem APplus Server :param settings: die Einstellungen für die Verbindung mit dem APplus Server
:type settings: APplusAppServerSettings :type settings: APplusAppServerSettings
""" """
def __init__(self, settings: APplusAppServerSettings) -> None: def __init__(self, settings: APplusServerSettings) -> None:
userEnv = settings.user userEnv = settings.user
if (settings.env): if (settings.env):
userEnv += "|" + settings.env userEnv += "|" + settings.env
session = Session() sessionApp = Session()
session.auth = HTTPBasicAuth(userEnv, "") sessionApp.auth = HTTPBasicAuth(userEnv, "")
self.transportApp = Transport(cache=SqliteCache(), session=sessionApp)
# self.transportApp = Transport(session=sessionApp)
if auth_negotiate_present:
sessionWeb = Session()
sessionWeb.auth = HttpNegotiateAuth(username=settings.webserverUser, password=settings.webserverPassword, domain=settings.webserverUserDomain)
self.transportWeb = Transport(cache=SqliteCache(), session=sessionWeb)
# self.transportWeb = Transport(session=sessionWeb)
else:
self.transportWeb = self.transportApp # führt vermutlich zu Authorization-Fehlern, diese sind aber zumindest hilfreicher als NULL-Pointer Exceptions
self.transport = Transport(cache=SqliteCache(), session=session)
# self.transport = Transport(session=session)
self.clientCache: Dict[str, Client] = {} self.clientCache: Dict[str, Client] = {}
self.settings = settings self.settings = settings
self.appserverUrl = "http://" + settings.appserver + ":" + str(settings.appserverPort) + "/" self.appserverUrl = "http://" + settings.appserver + ":" + str(settings.appserverPort) + "/"
def getClient(self, package: str, name: str) -> Client: def getAppClient(self, package: str, name: str) -> Client:
"""Erzeugt einen zeep - Client. """Erzeugt einen zeep - Client für den APP-Server.
Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden. Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden.
Wird als *package* "p2core" und als *name* "Table" verwendet und der Wird als *package* "p2core" und als *name* "Table" verwendet und der
resultierende client "client" genannt, dann kann resultierende client "client" genannt, dann kann
@ -76,11 +86,34 @@ class APplusServerConnection:
:return: den Client :return: den Client
:rtype: Client :rtype: Client
""" """
url = package+"/"+name cacheKey = "APP:"+package+"/"+name
try: try:
return self.clientCache[url] return self.clientCache[cacheKey]
except: except:
fullClientUrl = self.appserverUrl + url + ".jws?wsdl" fullClientUrl = self.appserverUrl + package+"/"+name + ".jws?wsdl"
client = Client(fullClientUrl, transport=self.transport) client = Client(fullClientUrl, transport=self.transportApp)
self.clientCache[url] = client self.clientCache[cacheKey] = client
return client
def getWebClient(self, url: str) -> Client:
"""Erzeugt einen zeep - Client für den Web-Server.
Mittels dieses Clients kann die von einer ASMX-Seite zur Verfügung gestellte Schnittstelle angesprochen werden.
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".
:param url: die relative URL der ASMX Seite, z.B. "masterdata/artikel.asmx"
:type package: str
:return: den Client
:rtype: Client
"""
if not auth_negotiate_present:
raise Exception("getWebClient ist nicht verfügbar, da Python-Package requests-negotiate-sspi nicht gefunden wurde")
cacheKey = "WEB:"+url
try:
return self.clientCache[cacheKey]
except:
fullClientUrl = self.settings.webserver + url + "?wsdl"
client = Client(fullClientUrl, transport=self.transportWeb)
self.clientCache[cacheKey] = client
return client return client

View File

@ -22,7 +22,7 @@ class APplusSysConf:
""" """
def __init__(self, server: 'APplusServer') -> None: def __init__(self, server: 'APplusServer') -> None:
self.client = server.getClient("p2system", "SysConf") self.client = server.getAppClient("p2system", "SysConf")
self.cache: Dict[str, type] = {} self.cache: Dict[str, type] = {}
def clearCache(self) -> None: def clearCache(self) -> None:

View File

@ -392,7 +392,7 @@ class SqlConditionBinComp(SqlConditionPrepared):
:type value2: SqlValue :type value2: SqlValue
""" """
def __init__(self, op: str, value1: SqlValue, 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") raise Exception("SqlConditionBinComp: value not provided")
cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2)) cond = "({} {} {})".format(formatSqlValue(value1), op, formatSqlValue(value2))