getWebClient implementiert

This commit is contained in:
Thomas Türk 2023-07-26 16:00:22 +02:00
parent b05b5de039
commit a1cc85a097
9 changed files with 113 additions and 55 deletions

View File

@ -1,5 +1,11 @@
# Changelog # 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 ## 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

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

@ -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.0"
authors = [ authors = [
{ name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" }, { name="Thomas Tuerk", email="kontakt@thomas-tuerk.de" },
] ]
@ -24,7 +24,8 @@ dependencies = [
'SQLAlchemy', 'SQLAlchemy',
'pandas', 'pandas',
'XlsxWriter', 'XlsxWriter',
'zeep' 'zeep',
'requests-negotiate-sspi'
] ]
[project.urls] [project.urls]

View File

@ -35,14 +35,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()
""" """
@ -60,9 +59,9 @@ 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.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")
def reconnectDB(self) -> None: def reconnectDB(self) -> None:
try: try:
@ -130,9 +129,9 @@ class APplusServer:
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 +144,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]:
""" """
@ -220,10 +232,10 @@ class APplusServer:
return self.client_nummer.service.nextNumber(obj) return self.client_nummer.service.nextNumber(obj)
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 +263,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

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