From a1a3d3105171b8e3fd300439cfd2ca3257b2961e Mon Sep 17 00:00:00 2001 From: "Asol.Projects auf Deeg ADM" Date: Wed, 26 Jul 2023 16:00:22 +0200 Subject: [PATCH] getWebClient implementiert --- Changelog.md | 6 ++ docs/source/anwendungen.rst | 6 +- examples/applus-server.yaml | 5 +- examples/read_settings.py | 2 + pyproject.toml | 5 +- src/PyAPplus64/applus.py | 51 +++++++++++------ src/PyAPplus64/applus_scripttool.py | 2 +- src/PyAPplus64/applus_server.py | 89 ++++++++++++++++++++--------- src/PyAPplus64/applus_sysconf.py | 2 +- 9 files changed, 113 insertions(+), 55 deletions(-) diff --git a/Changelog.md b/Changelog.md index 3b4cd9f..334c9b2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,11 @@ # Changelog +## 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 - Code-Cleanup mit Hilfe von flake8 - Bugfix: neue Python 3.10 Syntax entfernt diff --git a/docs/source/anwendungen.rst b/docs/source/anwendungen.rst index 9f80cac..747f779 100644 --- a/docs/source/anwendungen.rst +++ b/docs/source/anwendungen.rst @@ -85,10 +85,10 @@ Zugriff auf die Sysconf möglich:: print (server.sysconf.getList("STAMM", "EULAENDER")) 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, -zugegriffen werden:: +Beispielsweise kann auf die Sys-Config auch händisch, d.h. durch direkten Aufruf einer SOAP-Methode +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")) diff --git a/examples/applus-server.yaml b/examples/applus-server.yaml index a5ed87e..16970c1 100644 --- a/examples/applus-server.yaml +++ b/examples/applus-server.yaml @@ -16,7 +16,10 @@ appserver : { env : "default-umgebung" # hier wirklich Umgebung, nicht Mandant verwenden } 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 : { server : "some-server", diff --git a/examples/read_settings.py b/examples/read_settings.py index a00b9f9..bbce997 100644 --- a/examples/read_settings.py +++ b/examples/read_settings.py @@ -48,6 +48,8 @@ def main(confFile: Union[str, pathlib.Path], user: Optional[str] = None, env: Op print(" InstallPathWebServer:", server.scripttool.getInstallPathWebServer()) 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__": main(applus_configs.serverConfYamlTest) diff --git a/pyproject.toml b/pyproject.toml index 4955c37..4de3b88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "PyAPplus64" -version = "1.0.1" +version = "1.1.0" authors = [ { name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" }, ] @@ -24,7 +24,8 @@ dependencies = [ 'SQLAlchemy', 'pandas', 'XlsxWriter', - 'zeep' + 'zeep', + 'requests-negotiate-sspi' ] [project.urls] diff --git a/src/PyAPplus64/applus.py b/src/PyAPplus64/applus.py index 40e363f..a07bf4f 100644 --- a/src/PyAPplus64/applus.py +++ b/src/PyAPplus64/applus.py @@ -35,14 +35,13 @@ class APplusServer: """ def __init__(self, db_settings: applus_db.APplusDBSettings, - server_settings: applus_server.APplusAppServerSettings, - web_settings: applus_server.APplusWebServerSettings): + server_settings: applus_server.APplusServerSettings): self.db_settings: applus_db.APplusDBSettings = db_settings """Die Einstellungen für die Datenbankverbindung""" - self.web_settings: applus_server.APplusWebServerSettings = web_settings - """Die Einstellungen für die Datenbankverbindung""" + self.server_settings : applus_server.APplusServerSettings = server_settings + """Einstellung für die Verbindung zum APP- und Webserver""" self.db_conn = db_settings.connect() """ @@ -60,9 +59,9 @@ class APplusServer: self.scripttool: applus_scripttool.APplusScriptTool = applus_scripttool.APplusScriptTool(self) """erlaubt den einfachen Zugriff auf Funktionen des ScriptTools""" - self.client_table = self.server_conn.getClient("p2core", "Table") - self.client_xml = self.server_conn.getClient("p2core", "XML") - self.client_nummer = self.server_conn.getClient("p2system", "Nummer") + 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") def reconnectDB(self) -> None: try: @@ -130,9 +129,9 @@ class APplusServer: c = self.dbQuerySingleValue(sql, table) return (c > 0) - def getClient(self, package: str, name: str) -> Client: - """Erzeugt einen zeep - Client. - Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden. + def getAppClient(self, package: str, name: str) -> Client: + """Erzeugt einen zeep - Client für den APP-Server. + Mittels dieses Clients kann eines WSDL Schnittstelle des APP-Servers angesprochen werden. Wird als *package* "p2core" und als *name* "Table" verwendet und der resultierende client "client" genannt, dann kann z.B. mittels "client.service.getCompleteSQL(sql)" vom AppServer ein Vervollständigen @@ -145,7 +144,20 @@ class APplusServer: :return: den 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]: """ @@ -220,10 +232,10 @@ class APplusServer: return self.client_nummer.service.nextNumber(obj) 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") - url = str(self.web_settings.baseurl) + base + url = str(self.server_settings.webserver) + base firstArg = True for arg, argv in kwargs.items(): if not (argv is None): @@ -251,20 +263,21 @@ def applusFromConfigDict(yamlDict: Dict[str, Any], user: Optional[str] = None, e user = yamlDict["appserver"]["user"] if env is None or 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"], appserverPort=yamlDict["appserver"]["port"], user=user, # type: ignore - env=env) - web_server = applus_server.APplusWebServerSettings( - baseurl=yamlDict.get("webserver", {}).get("baseurl", None) - ) + env=env, + webserverUser=yamlDict.get("webserver", {}).get("user", None), + webserverUserDomain=yamlDict.get("webserver", {}).get("userDomain", None), + webserverPassword=yamlDict.get("webserver", {}).get("password", None)) dbparams = applus_db.APplusDBSettings( server=yamlDict["dbserver"]["server"], database=yamlDict["dbserver"]["db"], user=yamlDict["dbserver"]["user"], password=yamlDict["dbserver"]["password"]) - return APplusServer(dbparams, app_server, web_server) + return APplusServer(dbparams, server_settings) def applusFromConfigFile(yamlfile: 'FileDescriptorOrPath', diff --git a/src/PyAPplus64/applus_scripttool.py b/src/PyAPplus64/applus_scripttool.py index 7a5cb07..3b3b2c6 100644 --- a/src/PyAPplus64/applus_scripttool.py +++ b/src/PyAPplus64/applus_scripttool.py @@ -57,7 +57,7 @@ class APplusScriptTool: """ def __init__(self, server: APplusServer) -> None: - self.client = server.getClient("p2script", "ScriptTool") + self.client = server.getAppClient("p2script", "ScriptTool") def getCurrentDate(self) -> str: return self.client.service.getCurrentDate() diff --git a/src/PyAPplus64/applus_server.py b/src/PyAPplus64/applus_server.py index 5ba0031..db0ce01 100644 --- a/src/PyAPplus64/applus_server.py +++ b/src/PyAPplus64/applus_server.py @@ -13,56 +13,66 @@ from zeep.transports import Transport from zeep.cache import SqliteCache 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.appserverPort = appserverPort self.user = user - self.env = env + self.env = env - -class APplusWebServerSettings: - """ - Einstellungen, mit welchem APplus Web-Server sich verbunden werden soll. - """ - - def __init__(self, baseurl: Optional[str] = None): - self.baseurl: Optional[str] = baseurl + self.webserver = webserver + self.webserverUser = webserverUser + self.webserverUserDomain = webserverUserDomain + self.webserverPassword = webserverPassword try: - assert (isinstance(self.baseurl, str)) - if not (self.baseurl is None) and not (self.baseurl[-1] == "/"): - self.baseurl = self.baseurl + "/" + if not (self.webserver[-1] == "/"): + self.webserver = self.webserver + "/" except: pass 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 :type settings: APplusAppServerSettings """ - def __init__(self, settings: APplusAppServerSettings) -> None: + def __init__(self, settings: APplusServerSettings) -> None: userEnv = settings.user if (settings.env): userEnv += "|" + settings.env - session = Session() - session.auth = HTTPBasicAuth(userEnv, "") + sessionApp = Session() + 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.settings = settings self.appserverUrl = "http://" + settings.appserver + ":" + str(settings.appserverPort) + "/" - def getClient(self, package: str, name: str) -> Client: - """Erzeugt einen zeep - Client. + def getAppClient(self, package: str, name: str) -> Client: + """Erzeugt einen zeep - Client für den APP-Server. Mittels dieses Clients kann die WSDL Schnittstelle angesprochen werden. Wird als *package* "p2core" und als *name* "Table" verwendet und der resultierende client "client" genannt, dann kann @@ -76,11 +86,34 @@ class APplusServerConnection: :return: den Client :rtype: Client """ - url = package+"/"+name + cacheKey = "APP:"+package+"/"+name try: - return self.clientCache[url] + return self.clientCache[cacheKey] except: - fullClientUrl = self.appserverUrl + url + ".jws?wsdl" - client = Client(fullClientUrl, transport=self.transport) - self.clientCache[url] = client + fullClientUrl = self.appserverUrl + package+"/"+name + ".jws?wsdl" + client = Client(fullClientUrl, transport=self.transportApp) + 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 diff --git a/src/PyAPplus64/applus_sysconf.py b/src/PyAPplus64/applus_sysconf.py index 312a93a..adb154c 100644 --- a/src/PyAPplus64/applus_sysconf.py +++ b/src/PyAPplus64/applus_sysconf.py @@ -22,7 +22,7 @@ class APplusSysConf: """ def __init__(self, server: 'APplusServer') -> None: - self.client = server.getClient("p2system", "SysConf") + self.client = server.getAppClient("p2system", "SysConf") self.cache: Dict[str, type] = {} def clearCache(self) -> None: